NSButtonCell внутри пользовательских NSCell

В моем приложении какао мне нужен пользовательский NSCell для NSTableView. Этот подкласс NSCell содержит пользовательский NSButtonCell для обработки щелчка (и два или три NSTextFieldCell для текстового содержимого). Вы найдете упрощенный пример моего кода ниже.

@implementation TheCustomCell

- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView {
   // various NSTextFieldCells
   NSTextFieldCell *titleCell = [[NSTextFieldCell alloc] init];
   ....
   // my custom NSButtonCell
   MyButtonCell *warningCell = [[MyButtonCell alloc] init];
   [warningCell setTarget:self];
   [warningCell setAction:@selector(testButton:)];
   [warningCell drawWithFrame:buttonRect inView:controlView];
}

Проблема, с которой я застрял, заключается в следующем: каков наилучший / правильный способ заставить эту кнопку (точнее, NSButtonCell) внутри этой NSCell работать должным образом? "Работа" означает: вызвать назначенное сообщение действия и показать альтернативное изображение при нажатии. Из коробки кнопка ничего не делает при нажатии.

Информацию / чтения по этой теме найти сложно. Единственные сообщения, которые я нашел в сети, указывали мне на реализацию

- (BOOL)trackMouse:(NSEvent *)theEvent inRect:(NSRect)cellFrame ofView:(NSView *)controlView untilMouseUp:(BOOL)untilMouseUp; 

Это правильный способ сделать это??? Реализовать trackMouse: в моем содержании NSCell? А затем переслать событие в NSButtonCell? Я ожидал, что NSButtonCell сам узнает, что делать, когда на него щелкают (и я увидел trackMouse: методы, более совместимые с реально отслеживающими движениями мыши, а не как обучающее колесо для "стандартного" поведения нажатия). Но похоже, что он не делает этого, когда включен в саму ячейку... Кажется, я еще не понял общую картину пользовательских ячеек;-)

Я был бы рад, если бы кто-то мог ответить на это (или указать мне на какое-нибудь учебное пособие или подобное) из своего собственного опыта - и сказать мне, если я на правильном пути.

Спасибо заранее, Тоби

2 ответа

Решение

Минимальные требования:

  • После того, как левая мышь нажата на кнопку, она должна быть нажата, когда мышь находится над ней.
  • Если мышь отпустит кнопку, ваша ячейка должна отправить соответствующее сообщение о действии.

Чтобы кнопка выглядела нажатой, вам нужно обновить ячейку кнопки highlighted собственность в зависимости от обстоятельств. Изменение только состояния не приведет к этому, но вы хотите, чтобы кнопка была выделена, если и только если ее состояния NSOnState,

Чтобы отправить сообщение о действии, вы должны знать, когда мышь отпущена, а затем использовать -[NSApplication sendAction:to:from:] отправить сообщение.

Чтобы иметь возможность отправлять эти сообщения, вам необходимо подключиться к методам отслеживания событий, предоставляемым NSCell, Обратите внимание, что все эти методы отслеживания, кроме финального, -stopTracking:... метод, верните логическое значение, чтобы ответить на вопрос "Хотите ли вы продолжать получать сообщения отслеживания?"

Последний поворот заключается в том, что для того, чтобы отправлять какие-либо сообщения отслеживания вообще, вам необходимо реализовать -hitTestForEvent:inRect:ofView: и вернуть соответствующую битовую маску NSCellHit... ценности. В частности, если возвращаемое значение не имеет NSCellHitTrackableArea значение в нем, вы не получите никаких сообщений отслеживания!

Итак, на высоком уровне ваша реализация будет выглядеть примерно так:

- (NSUInteger)hitTestForEvent:(NSEvent *)event
                       inRect:(NSRect)cellFrame
                       ofView:(NSView *)controlView {
    NSUInteger hitType = [super hitTestForEvent:event inRect:cellFrame ofView:controlView];

    NSPoint location = [event locationInWindow];
    location = [controlView convertPointFromBase:location];
    // get the button cell's |buttonRect|, then
    if (NSMouseInRect(location, buttonRect, [controlView isFlipped])) {
        // We are only sent tracking messages for trackable areas.
        hitType |= NSCellHitTrackableArea;
    }
    return hitType;
}

+ (BOOL)prefersTrackingUntilMouseUp {
   // you want a single, long tracking "session" from mouse down till up
   return YES;
}

- (BOOL)startTrackingAt:(NSPoint)startPoint inView:(NSView *)controlView {
   // use NSMouseInRect and [controlView isFlipped] to test whether |startPoint| is on the button
   // if so, highlight the button
   return YES;  // keep tracking
}

- (BOOL)continueTracking:(NSPoint)lastPoint at:(NSPoint)currentPoint inView:(NSView *)controlView {
   // if |currentPoint| is in the button, highlight it
   // otherwise, unhighlight it
   return YES;  // keep on tracking
}

- (void)stopTracking:(NSPoint)lastPoint at:(NSPoint)stopPoint inView:(NSView *)controlView mouseIsUp:(BOOL)flag {
   // if |flag| and mouse in button's rect, then
   [[NSApplication sharedApplication] sendAction:self.action to:self.target from:controlView];
   // and, finally,
   [buttonCell setHighlighted:NO];
}

Точка NSCell Подклассы должны отделить ответственность за визуализацию и обработку общих элементов пользовательского интерфейса (элементов управления) от визуальной и иерархической ответственности NSView классы. Такое спаривание позволяет каждому обеспечить большую специализацию и изменчивость, не обременяя другого. Посмотрите на большое количество NSButton экземпляры можно создать в Какао. Представьте себе количество NSButton Подклассы, которые существовали бы, если бы этот раздел функциональности отсутствовал!

Использование языка шаблонов проектирования для описания ролей: NSControl действует как фасад, скрывая детали своей композиции от своих клиентов и передавая события и передавая сообщения своим NSCell экземпляр, который действует как делегат.

Потому что ваш NSCell подкласс включает в себя другие NSCell экземпляры подкласса в его составе, они больше не получают эти сообщения о событиях напрямую от NSControl экземпляр, который находится в иерархии представления. Таким образом, чтобы эти экземпляры ячеек могли получать сообщения о событиях из цепочки респондента (в иерархии представлений), экземпляр вашей ячейки должен передавать эти соответствующие события. Вы воссоздаете работу NSView иерархия.

Это не обязательно плохо. Повторяя поведение NSControl (И его NSView суперкласс), но в NSCell Форма, вы можете фильтровать события, передаваемые в ваши подэлементы по местоположению, типу события или другим критериям. Недостатком является копирование работы NSView/NSControl в создании механизма фильтрации и управления.

Таким образом, при разработке вашего интерфейса, вы должны учитывать, NSButtonCell (а также NSTextFieldCells) лучше в NSControlS в иерархии нормального представления, или как подэлементы в вашем NSCell подкласс. Лучше использовать функциональность, которая уже существует для вас в кодовой базе, чем без необходимости заново ее изобретать (и продолжать поддерживать ее позже).

Другие вопросы по тегам