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

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

Заранее спасибо, Тоби


person Tobidobi    schedule 17.02.2010    source источник


Ответы (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];
}
person Jeremy W. Sherman    schedule 17.07.2010
comment
Где вы указываете таблице, чтобы она сообщала своему источнику данных, что кнопка была проверена? - person Richard; 25.12.2011
comment
@ Джереми В. Шерман: Наверное, это глупый вопрос, но как получить | buttonRect | ячейки кнопки? Я пробовал разные вещи, например [рамка кнопки], но, похоже, это не сработало ... - person houbysoft; 28.02.2012

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

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

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

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

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

person Huperniketes    schedule 26.02.2010
comment
@Huperniketes, поэтому, если я собирался реализовать настраиваемую ячейку для таблицы (то есть до Lion с новой возможностью ячейки таблицы NSView), я вынужден в основном имитировать поведение каждого элемента управления, который я встраиваю, и я просто использую ячейку для рисование на самом деле. Это правильно? - person David; 05.11.2011
comment
@ Дэвид, я не понимаю твой вопрос. Если вы спрашиваете о встраивании подкласса NSView в настраиваемую ячейку в табличном представлении, ваша ячейка должна соответствовать протоколу NSControl, либо обрабатывать сообщения из самого подкласса представления, передавая их в табличное представление, либо оставляя их. заглушен. Вам не нужно встраивать подкласс NSControl в настраиваемую ячейку, поскольку обычно вы можете использовать саму ячейку элемента управления. Что касается поведения ячейки, вы можете использовать ячейку для рисования частей, поиска частей, на которые нажимали, и т. Д. - person Huperniketes; 20.01.2012
comment
@Huperniketes я хочу узнать, как создать, скажем, ячейку, содержащую другие элементы управления. Кажется, что я мог бы использовать NSButtonCell для рисования элемента управления, но фактическая функциональность кнопки должна быть воссоздана, т.е. мне пришлось бы обнаруживать щелчки (для кнопки), перетаскивания мыши для NSSlider и т. Д. Похоже, много работы для что-то, что я думал, будет более простым / многоразовым. Я что-то упускаю? - person David; 20.01.2012
comment
@David, здесь задействованы два класса объектов: элементы управления и ячейки. Ячейки (такие как NSButtonCell, NSImageCell, NSTokenFieldCell и т. Д.) Выполняют основную часть работы: отрисовку, проверку попадания, вычисление размера ячеек и т. Д. Однако им не хватает связи с иерархией представления: положение на экране, получение события из иерархии представления (подкласс NSResponder) и т. д. Это обеспечивается NSControl, который действует как прокси при перенаправлении сообщений в ячейку и как диспетчер, если у него более одной. Ваша ячейка может содержать либо ячейки, либо элементы управления, но вы должны передавать нужные сообщения в оба. - person Huperniketes; 26.01.2012