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
(а также NSTextFieldCell
s) лучше в NSControl
S в иерархии нормального представления, или как подэлементы в вашем NSCell
подкласс. Лучше использовать функциональность, которая уже существует для вас в кодовой базе, чем без необходимости заново ее изобретать (и продолжать поддерживать ее позже).