NSTextField 在 NSTableCellView 内部时不调用委托

发布于 2024-11-30 19:23:14 字数 3024 浏览 0 评论 0原文

我的应用程序中有一个相当普通的源列表(从对象库中拖出),以 NSTreeController 作为其数据源。我将 DataCell 内的 NSTextField 设置为可编辑,但我希望能够对某些单元格关闭该功能。我认为您会这样做的方式是使用 NSTextField 的委托,但我尝试过的委托方法都没有被调用。我有什么遗漏的吗?我在 XIB 中设置了带有插座的委托,它恰好也是所有者 NSOutlineView 的委托,同时实现了 NSOutlineViewDelegateNSTextFieldDelegate 协议。

另外,我也无法使用旧的 –outlineView:shouldEditTableColumn:item: NSOutlineViewDelegate 方法,因为它仅适用于基于单元格的大纲视图(I'我假设情况是这样 - 大纲视图文档似乎没有针对 Lion 进行更新,尽管类似的 NSTableView 文档已经更新,并且这些方法也不会被调用) 。

更新

我在一个全新的测试项目中重现了这个,所以它绝对与我的任何自定义类都不相关。按照以下步骤创建我的示例项目,并重现此问题。

  1. 在 Xcode 4.1 中,创建一个 Mac OS X Cocoa Application 类型的新项目,不选择任何特殊选项
  2. 创建两个新文件,SourceListDataSource.mSourceListDelegate.m,其中下面指定的内容
  3. 在 MainMenu.xib 中,将 Source List 拖到窗口上
  4. 将两个 Object 拖到 Dock(窗口左侧)上,指定其中一个为 SourceListDataSource 类,另一个为 SourceListDelegate
  5. 将大纲视图的 dataSourcedelegate 出口连接到这两个 打开
  6. 为大纲视图列内的 DataCell 视图选择静态文本 NSTextField
  7. Value 绑定,保留默认设置
  8. 连接其 delegate 出口到来源列表委托对象
  9. 将其 Behavior 属性设置为 Editable
  10. Build and Run,然后在大纲视图中的任一单元格上单击两次。

预期:该字段不可编辑,并且有一个“嗯,我应该吗?”日志中的消息

实际:该字段是可编辑的,并且没有记录任何消息

这是框架中的错误,还是我应该以不同的方式实现此目的?


SourceListDataSource.m

#import <Cocoa/Cocoa.h>

@interface SourceListDataSource : NSObject <NSOutlineViewDataSource>

@property (retain) NSArray *items;

@end

@implementation SourceListDataSource

@synthesize items;

- (id)init
{
    self = [super init];
    if (self) {
        items = [[NSArray arrayWithObjects:@"Alo", @"Homora", nil] retain];
    }

    return self;
}

- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
    if (!item) {
        return [self.items objectAtIndex:index];
    }

    return nil;
}

- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
    return !item ? self.items.count : 0;
}

- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
    return NO;
}

- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
    return item;
}

@end

SourceListDelegate.m

#import <Foundation/Foundation.h>

@interface SourceListDelegate : NSObject <NSOutlineViewDelegate, NSTextFieldDelegate> @end

@implementation SourceListDelegate

- (NSTableRowView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item {
    return [outlineView makeViewWithIdentifier:@"DataCell" owner:self];
}

- (BOOL)control:(NSControl *)control textShouldBeginEditing:(NSText *)fieldEditor {
    NSLog(@"well, should I?");
    return NO;
}

@end

I have a fairly vanilla Source List (dragged out from the Object Library) in my app, with an NSTreeController as its data source. I set the NSTextField inside the DataCell to be editable, but I want to be able to turn that off for some cells. The way I figured you would do this, is with a delegate for the NSTextField, but none of the delegate methods I've tried get called. Is there something I'm missing? I have the delegate set with an outlet in my XIB, and it happens to be the delegate to the owner NSOutlineView, as well, implementing both the NSOutlineViewDelegate and NSTextFieldDelegate protocols.

Also, I can't use the old –outlineView:shouldEditTableColumn:item: NSOutlineViewDelegate method either, since that only works with cell-based Outline Views (I'm assuming this is the case - the Outline View documentation doesn't appear to have been updated for Lion, though the analogous NSTableView documentation has, and those methods don't get called either).

Update

I reproduced this in a brand new test project, so it's definitely not related to any of my custom classes. Follow the steps below to create my sample project, and reproduce this problem.

  1. In Xcode 4.1, create a new project, of type Mac OS X Cocoa Application, with no special options selected
  2. Create two new files, SourceListDataSource.m and SourceListDelegate.m, with the contents specified below
  3. In MainMenu.xib, drag a Source List onto the Window
  4. Drag two Objects onto the dock (left side of the window), specifying the SourceListDataSource class for one, and the SourceListDelegate for the other
  5. Connect the Outline View's dataSource and delegate outlets to those two objects
  6. Select the Static Text NSTextField for the DataCell view inside the outline view's column
  7. Turn on its Value binding, keeping the default settings
  8. Connect its delegate outlet to the Source List Delegate object
  9. Set its Behavior property to Editable
  10. Build and Run, then click twice on either cell in the outline view.

Expected: The field is not editable, and there is a "well, should I?" message in the log

Actual: The field is editable, and no messages are logged

Is this a bug in the framework, or am I supposed to achieve this a different way?


SourceListDataSource.m

#import <Cocoa/Cocoa.h>

@interface SourceListDataSource : NSObject <NSOutlineViewDataSource>

@property (retain) NSArray *items;

@end

@implementation SourceListDataSource

@synthesize items;

- (id)init
{
    self = [super init];
    if (self) {
        items = [[NSArray arrayWithObjects:@"Alo", @"Homora", nil] retain];
    }

    return self;
}

- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
    if (!item) {
        return [self.items objectAtIndex:index];
    }

    return nil;
}

- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
    return !item ? self.items.count : 0;
}

- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
    return NO;
}

- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
    return item;
}

@end

SourceListDelegate.m

#import <Foundation/Foundation.h>

@interface SourceListDelegate : NSObject <NSOutlineViewDelegate, NSTextFieldDelegate> @end

@implementation SourceListDelegate

- (NSTableRowView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item {
    return [outlineView makeViewWithIdentifier:@"DataCell" owner:self];
}

- (BOOL)control:(NSControl *)control textShouldBeginEditing:(NSText *)fieldEditor {
    NSLog(@"well, should I?");
    return NO;
}

@end

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

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

发布评论

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

评论(3

浴红衣 2024-12-07 19:23:15

子类 NSTableCellView,带有文本字段的出口,并在 awakeFromNib 中设置文本字段委托。完成此操作后,control:textShouldBeginEditing: 被调用。我不知道为什么,但是(编辑:)如果您在 xib 中设置委托,则不会调用委托方法 - 我和您有相同的经历。

或者,您可以放弃委托,并使用绑定(到模型的布尔属性)有条件地设置可编辑,或者使用作用于模型实例并返回布尔值的值转换器。使用文本字段的可编辑绑定。

Subclass NSTableCellView, with an outlet for the text field, and set the text field delegate in awakeFromNib. After doing that, control:textShouldBeginEditing: gets called. I'm not sure why, but (edit:) if you set the delegate in the xib, the delegate methods aren't called – I had the same experience as you.

Alternatively, you can forego the delegate and conditionally set Editable using a binding, either to a boolean property of the model, or using a value transformer which acts on a model instance and returns a boolean. Use the Editable binding of the text field.

旧时模样 2024-12-07 19:23:15

我也遇到过这个问题。因为我不想丢失绑定,所以我执行了以下操作:

将 TextField 的 editable 绑定到 objectValue 并设置自定义 NSValueTransformer > 子类。

I've encountered this problem, too. Because I didn't want to lose the bindings, I did the following:

Binding editable of the TextField to the objectValue and set up a custom NSValueTransformer subclass.

¢好甜 2024-12-07 19:23:15

上面提出的其他解决方案性能不佳,并且不适用于现代版本的 macOS。当即将编辑一个文本字段时,NSTableView 会在整个表格中的每个文本字段上调用 ​​acceptsFirstResponder。当您滚动表格时,第一响应者方法就会被调用。如果您在这些调用中添加一些日志记录,您将看到它们的运行情况。

此外,不需要在 IB 之外的任何地方分配 textField 的委托,并且实际上不会起作用,因为 NSTableView (因此 NSOutlineView)基本上“接管”它们包含的视图。

正确的现代方法:

子类 NSTableView (或 NSOutlineView)并执行以下操作:

final class MyTableView: NSTableView
{
    override func validateProposedFirstResponder(_ responder: NSResponder, for event: NSEvent?) -> Bool
    {
        // NSTableView calls -validateProposedResponder on cellViews' textFields A METRIC TON, even while just scrolling around, therefore
        // do not interfere unless we're evaluating a CLICK on a textField.
        if let textField: NSTextField = responder as? NSTextField,
           (event?.type == .leftMouseDown || event?.type == .rightMouseDown)
        {
            // Don't just automatically clobber what the TableView returns; it'll return false here when delays are needed for double-actions, etc.
            let result: Bool = super.validateProposedFirstResponder(responder, for: event)
            
            // IF the tableView thinks this textField should edit, now we can ask the textField's delegate to confirm that.
            if result == true
            {
                print("Validate first responder called: \(responder).")
                return textField.delegate?.control?(textField, textShouldBeginEditing: textField.window?.fieldEditor(true, for: nil) ?? NSText()) ?? result
            }
            
            return result
        }
        else
        {
            return super.validateProposedFirstResponder(responder, for: event)
        }
    }
}

注意:

  1. 这是针对 macOS 11.3.1 和 Xcode 12.5 针对应用程序目标编写的macOS 11。

  2. NSTableCellViews 中 NSTextFields 的 isEditable 属性必须设置为 true。 NSTableView 的 -validateFirstResponder 实现将首先检查该属性,因此您无需在委托方法中执行此操作。

The other proposed solutions above are not performant and will not work on modern versions of macOS. NSTableView calls acceptsFirstResponder on EVERY textField in the entire table when one is about to be edited. And first responder methods get called while you just scroll around the table. If you put some logging in those calls, you'll see them in action.

Additionally, assigning the textField's delegate anywhere other than IB is not needed and won't actually work because NSTableView (and therefore NSOutlineView) basically "take over" for the views they contain.

The Correct, Modern Approach:

Subclass NSTableView (or NSOutlineView) and do this:

final class MyTableView: NSTableView
{
    override func validateProposedFirstResponder(_ responder: NSResponder, for event: NSEvent?) -> Bool
    {
        // NSTableView calls -validateProposedResponder on cellViews' textFields A METRIC TON, even while just scrolling around, therefore
        // do not interfere unless we're evaluating a CLICK on a textField.
        if let textField: NSTextField = responder as? NSTextField,
           (event?.type == .leftMouseDown || event?.type == .rightMouseDown)
        {
            // Don't just automatically clobber what the TableView returns; it'll return false here when delays are needed for double-actions, etc.
            let result: Bool = super.validateProposedFirstResponder(responder, for: event)
            
            // IF the tableView thinks this textField should edit, now we can ask the textField's delegate to confirm that.
            if result == true
            {
                print("Validate first responder called: \(responder).")
                return textField.delegate?.control?(textField, textShouldBeginEditing: textField.window?.fieldEditor(true, for: nil) ?? NSText()) ?? result
            }
            
            return result
        }
        else
        {
            return super.validateProposedFirstResponder(responder, for: event)
        }
    }
}

Notes:

  1. This was written against macOS 11.3.1 and Xcode 12.5 for an application targeting macOS 11.

  2. The isEditable property of the NSTextFields in your NSTableCellViews must be set to true. NSTableView's implementation of -validateFirstResponder will check that property first, so you need not do so in your delegate method.

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