【问题标题】:NSTextField not calling delegate when inside an NSTableCellViewNSTextField 在 NSTableCellView 内时不调用委托
【发布时间】:2011-08-19 12:10:48
【问题描述】:

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

另外,我也不能使用旧的 –outlineView:shouldEditTableColumn:item: NSOutlineViewDelegate 方法,因为它只适用于基于单元格的大纲视图(我假设是这种情况 - 大纲视图文档没有'虽然类似的 NSTableView 文档已经为 Lion 更新了 t,但这些方法也没有被调用)。

更新

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

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

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

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

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


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

【问题讨论】:

标签: objective-c cocoa osx-lion nstextfield nsoutlineview


【解决方案1】:

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

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

【讨论】:

  • 你在我写我的示例项目时回答了。绑定,FTW。我没有想到这一点,这实际上是比为此目的创建委托更好的解决方案。我想下一步是提交雷达,因为我很确定插座不工作是一个错误。
  • 实际上,除了绑定之外,我最终还想要委托功能,而您答案的第一段似乎并不适用。当我对NSTextField 进行子类化时,我看到委托确实被设置了,但是委托方法没有被调用。我覆盖了-acceptsFirstResponder 以返回代表的control:textShouldBeginEditing:
  • 是在 awakeFromNib 中设置时发生的,还是在使用插座时发生的?
  • 就在我设置常规代理出口的时候。
  • 我编辑了第一段以反映这一点。奇怪的。听起来仍然像一个错误。
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

    上述其他建议的解决方案性能不佳,并且不适用于现代版本的 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 实现将首先检查该属性,因此您无需在委托方法中这样做。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-08-25
      • 1970-01-01
      • 2021-12-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多