在我的cocoa应用程序中,我需要一个用于NSTableView的自定义NSCell。此 NSCell子类包含用于处理单击的自定义NSButtonCell (以及用于文本内容的两个或三个NSTextFieldCells)。您将在下面找到我的代码的简化示例。
@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];
}
我遇到的问题是:在这个NSCell内部获得该按钮(更确切地说:NSButtonCell)的最佳/正确方法是什么才能正常工作?“工作”意味着:触发已分配的操作消息,并在单击时显示备用图像。开箱即用,点击时该按钮不会执行任何操作。
很难找到有关此主题的信息/阅读材料。我在网上找到的唯一帖子指出我要实施
- (BOOL)trackMouse:(NSEvent *)theEvent inRect:(NSRect)cellFrame ofView:(NSView *)controlView untilMouseUp:(BOOL)untilMouseUp;
这是正确的方法吗? 实现trackMouse:在我的包含NSCell中?然后将事件转发给NSButtonCell?我本来期望NSButtonCell本身知道在点击它时该做什么(我看到trackMouse:方法更多的是在cunjunction,真正跟踪鼠标移动 - 而不是作为'标准'点击行为的训练轮)。但是当它包含在一个单元格本身时似乎不会这样做...... 看来我还没有掌握自定义单元格的大图,但是; - )
如果有人能够根据自己的经验回答这个问题(或指向我的某些教程等),我会很高兴 - 并告诉我我是否走在正确的轨道上。< / p>
提前致谢, 托比
答案 0 :(得分:8)
最低要求是:
要按下按钮,您需要根据需要更新按钮单元格的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];
}
答案 1 :(得分:5)
NSCell
子类的要点是分离渲染和处理公共UI元素(控件)与视觉和事件层次结构的责任
NSView
类的职责。这种配对允许每个配对提供更大的专业性和可变性而不会给另一个带来负担。查看可以在Cocoa中创建的大量NSButton
个实例。想象一下,如果缺少功能上的这种分割,那么NSButton
子类的数量就会存在!
使用设计模式语言来描述角色:NSControl
充当外观,从其客户端隐藏其组成的详细信息,并将事件和呈现消息呈现给充当委托的NSCell
实例
因为您的NSCell
子类在其组合中包含其他NSCell
子类实例,所以它们不再直接从视图层次结构中的NSControl
实例接收这些事件消息。因此,为了使这些单元实例从事件响应器链(视图层次结构)接收事件消息,您的单元实例需要传递这些相关事件。您正在重新创建NSView
层次结构的工作。
这不一定是坏事。通过以NSControl
形式复制NSView
(及其NSCell
超类)的行为,您可以按位置,事件类型或其他方式过滤传递到子单元格的事件标准。缺点是复制NSView/NSControl
在构建过滤和放大过程中的工作。管理机制。
因此,在设计界面时,您需要考虑NSButtonCell
(和NSTextFieldCell
s)在普通视图层次结构中的NSControl
中是否更好,或者作为子单元格在您的NSCell
子类中。最好在代码库中利用已经存在的功能,而不是不必要地重新创建它(并在以后继续维护它)。