10

在我的可可应用程序中,我需要一个用于 NSTableView 的自定义 NSCell。这个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];
}

我遇到的问题是:让这个 NSCell 中的 Button(更准确地说:NSButtonCell)正常工作的最佳/正确方法是什么?“工作”是指:触发指定的操作消息并在单击时显示备用图像。开箱即用,单击时按钮不执行任何操作。

很难找到有关此主题的信息/阅读材料。我在网上找到的唯一帖子指向我实施

- (BOOL)trackMouse:(NSEvent *)theEvent inRect:(NSRect)cellFrame ofView:(NSView *)controlView untilMouseUp:(BOOL)untilMouseUp; 

这是正确的方法吗???实现 trackMouse: 在我包含的 NSCell 中?然后将事件转发到 NSButtonCell?我本来希望 NSButtonCell 本身知道当它被点击时要做什么(我看到 trackMouse: 方法更多地与真正跟踪鼠标移动相结合——而不是作为“标准”点击行为的训练轮)。但是,当包含在单元格本身中时,它似乎并没有这样做……看来我还没有掌握自定义单元格的大局;-)

如果有人能根据他自己的经验回答这个问题(或向我指出一些教程等),我会很高兴 - 并告诉我我是否走在正确的轨道上

在此先感谢,托比

4

2 回答 2

8

最低要求是:

  • 将鼠标左键放在按钮上后,只要鼠标悬停在按钮上,它就必须显示为按下状态。
  • 如果鼠标随后释放到按钮上,您的单元格必须发送适当的操作消息。

要使按钮看起来被按下,您需要根据需要更新按钮单元格的highlighted属性。仅更改状态不会完成此操作,但您想要的是当且仅当其状态为 时,按钮才会突出显示NSOnState

要发送动作消息,您需要知道何时释放鼠标,然后使用-[NSApplication sendAction:to:from:]发送消息。

为了能够发送这些消息,您需要挂钩NSCell. 请注意,除了 final 方法之外,所有这些跟踪方法都-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];
}
于 2010-07-17T04:43:31.863 回答
5

子类的重点NSCell是将渲染和处理公共 UI 元素(控件)的职责与NSView类的视觉和事件层次结构职责分开。这种配对允许每个人提供更大的专业化和可变性,而不会给另一个人带来负担。看看可以在 Cocoa 中创建的大量NSButton实例。想象一下,NSButton如果没有这种功能拆分,将会存在的子类数量!

使用设计模式语言来描述角色:anNSControl充当外观,向其客户隐藏其组成的细节,并将事件传递给NSCell充当委托的实例并呈现消息。

因为您的NSCell子类在其组合中包含其他NSCell子类实例,所以它们不再直接从NSControl视图层次结构中的实例接收这些事件消息。因此,为了让这些单元实例从(视图层次结构的)事件响应者链接收事件消息,您的单元实例需要传递这些相关事件。您正在重新创建NSView层次结构的工作。

这不一定是坏事。通过在表单中​​复制NSControl(及其NSView超类)的行为NSCell,您可以按位置、事件类型或其他条件过滤传递给子单元的事件。缺点是重复NSView/NSControl构建过滤和管理机制的工作。

所以在设计你的界面时,你需要考虑NSButtonCell(和s) 是在普通视图层次结构中的 s 中NSTextFieldCell更好,还是作为子类中的子单元格。最好利用代码库中已经存在的功能,而不是不必要地重新发明它(并在以后继续维护它)。NSControlNSCell

于 2010-02-26T20:26:31.273 回答