NSButtonCell внутри пользовательских NSCell - PullRequest
9 голосов
/ 17 февраля 2010

в моем приложении какао мне нужен пользовательский 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: методы, более совместимые с реально отслеживающими движениями мыши, а не как тренировочное колесо для «стандартного» поведения нажатия). Но похоже, что он не делает этого, когда включен в саму клетку ... Кажется, я еще не понял общую картину пользовательских ячеек; -)

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

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

Ответы [ 2 ]

8 голосов
/ 17 июля 2010

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

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

Чтобы кнопка выглядела нажатой, вам необходимо соответствующим образом обновить свойство 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];
}
5 голосов
/ 26 февраля 2010

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

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

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

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

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

...