自定义 NSCell 内的 NSButtonCell

发布于 2024-08-21 20:28:12 字数 1265 浏览 6 评论 0原文

在我的可可应用程序中,我需要一个用于 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; 

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

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

提前致谢, 飞鸟

in my cocoa application, I need a custom NSCell for an NSTableView. This NSCell subclass contains a custom NSButtonCell for handling a click (and two or three NSTextFieldCells for textual contents). You'll find a simplified example of my code below.

@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];
}

The problem I'm stuck with is: what is the best/right way to get that Button (more precisely: the NSButtonCell) inside this NSCell to work properly? "work" means: trigger the assigned action message and show the alternate image when clicked. Out of the box, the button doesn't do anything when clicked.

Information / readings on this topic is hard to find. The only posts I found on the net pointed me to implementing

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

Is this the correct way to do it??? Implement trackMouse: in my containing NSCell? And then forward the event to the NSButtonCell? I would have expected the NSButtonCell itself to know what to do when it's being clicked (and I saw the trackMouse: methods more in cunjunction with really tracking mouse movements - not as a training wheel for 'standard' click behaviour). But it seems like it doesn't do this when included in a cell itself...
It seems I haven't grasped the big picture on custom cells, yet ;-)

I'd be glad if someone could answer this (or point me to some tutorial or the like) out of his own experience - and tell me if I'm on the right track.

Thanks in advance,
Tobi

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

_畞蕅 2024-08-28 20:28:12

最低要求是:

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

要使按钮看起来被按下,您需要根据需要更新按钮单元格的 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];
}

The minimal requirements are:

  • After left mouse down on the button, it must appear pressed whenever the mouse is over it.
  • If the mouse then releases over the button, your cell must send the appropriate action message.

To make the button look pressed, you need to update the button cell's highlighted property as appropriate. Changing the state alone will not accomplish this, but what you want is for the button to be highlighted if, and only if, its states is NSOnState.

To send the action message, you need to be aware of when the mouse is released, and then use -[NSApplication sendAction:to:from:] to send the message.

In order to be in position to send these messages, you will need to hook into the event tracking methods provided by NSCell. Notice that all those tracking methods, except the final, -stopTracking:... method, return a Boolean to answer the question, "Do you want to keep receiving tracking messages?"

The final twist is that, in order to be sent any tracking messages at all, you need to implement -hitTestForEvent:inRect:ofView: and return an appropriate bitmask of NSCellHit... values. Specifically, if the value returned doesn't have the NSCellHitTrackableArea value in it, you won't get any tracking messages!

So, at a high level, your implementation will look something like:

- (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];
}
空心↖ 2024-08-28 20:28:12

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

使用设计模式语言来描述角色:NSControl 充当外观,向客户端隐藏其组成的详细信息,并将事件和渲染消息传递到其充当角色的 NSCell 实例作为代表。

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

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

因此,在设计界面时,您需要考虑 NSButtonCell (和 NSTextFieldCell)是否在普通视图层次结构中的 NSControl 中更好,或者作为 NSCell 子类中的子单元。最好利用代码库中已有的功能,而不是不必要地重新发明它(并在以后继续维护它)。

The point of NSCell subclasses is to separate responsibility for rendering and handling common UI elements (the controls) from the visual- and event-hierarchy
responsibilities of the NSView classes. This pairing permits each one to provide greater specialization and variability without burdening the other. Look at the large number of NSButton instances one can create in Cocoa. Imagine the number of NSButton sub-classes that would exist if this split in functionality were absent!

Using design pattern language to describe the roles: an NSControl acts as a façade, hiding details of its composition from its clients and passing events and rendering messages to its NSCell instance which acts as a delegate.

Because your NSCell subclass includes other NSCell subclass instances within its composition, they no longer directly receive these event messages from the NSControl instance which is in the view hierarchy. Thus, in order for these cell instances to receive event messages from the event responder chain (of the view hierarchy), your cell instance needs to pass along those relevant events. You are recreating the work of the NSView hierarchy.

This isn't necessarily a bad thing. By replicating the behavior of NSControl (and its NSView superclass) but in an NSCell form, you can filter the events passed on to your sub-cells by location, event type, or other criteria. The drawback is replicating the work of NSView/NSControl in building the filtering & management mechanism.

So in designing your interface, you need to consider whether the NSButtonCell (and NSTextFieldCells) are better off in NSControls in the normal view hierarchy, or as sub-cells in your NSCell subclass. It's better to leverage the functionality which already exists for you in a codebase than to re-invent it (and continue maintaining it later) unnecessarily.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文