NSButtonCell内部定制NSCell

时间:2010-02-17 11:53:32

标签: cocoa tableview nscell nsbuttoncell

在我的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>

提前致谢, 托比

2 个答案:

答案 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子类中。最好在代码库中利用已经存在的功能,而不是不必要地重新创建它(并在以后继续维护它)。