NSTextField在NSTableCellView内部时不调用委托

时间:2011-08-19 12:10:49

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

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

另外,我也不能使用旧的–outlineView:shouldEditTableColumn:item: NSOutlineViewDelegate方法,因为这只适用于基于单元格的大纲视图(我假设是这种情况 - 大纲虽然类似的NSTableView文档有,但看起来文档似乎没有为Lion更新过,而且这些方法也没有被调用

更新

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

  1. 在Xcode 4.1中,创建一个类型为Mac OS X Cocoa Application的新项目,未选择任何特殊选项
  2. 使用下面指定的内容创建两个新文件 SourceListDataSource.m SourceListDelegate.m
  3. 在MainMenu.xib中,将Source List拖到窗口
  4. 将两个Object拖到停靠栏(窗口左侧)上,为其中一个指定SourceListDataSource类,为另一个指定SourceListDelegate
  5. 将大纲视图的dataSourcedelegate出口连接到这两个对象
  6. 在大纲视图的列
  7. 中选择DataCell视图的静态文本NSTextField
  8. 启用其Value绑定,保持默认设置
  9. 将其delegate出口连接到“源列表委派”对象
  10. 将其Behavior属性设置为可编辑
  11. 构建并运行,然后在大纲视图中的任一单元格上单击两次。
  12. 预期:该字段不可编辑,并且有一个“好吧,我应该吗?”日志中的消息

    实际:该字段可编辑,不会记录任何消息

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


    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
    

3 个答案:

答案 0 :(得分:3)

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

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

答案 1 :(得分:0)

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

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

答案 2 :(得分:0)

上述其他提议的解决方案性能不佳,无法在现代版本的 macOS 上运行。 NSTableView 在整个表格中的每个 textField 即将被编辑时调用 acceptsFirstResponder。当您在表格中滚动时,第一响应者方法会被调用。如果您在这些调用中进行一些日志记录,您就会看到它们正在运行。

此外,不需要将 textField 的委托分配给 IB 以外的任何地方,并且实际上不会起作用,因为 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 实现将首先检查该属性,因此您无需在委托方法中这样做。