NSTextField 在 NSTableCellView 内部时不调用委托
我的应用程序中有一个相当普通的源列表(从对象库中拖出),以 NSTreeController 作为其数据源。我将 DataCell 内的 NSTextField
设置为可编辑,但我希望能够对某些单元格关闭该功能。我认为您会这样做的方式是使用 NSTextField
的委托,但我尝试过的委托方法都没有被调用。我有什么遗漏的吗?我在 XIB 中设置了带有插座的委托,它恰好也是所有者 NSOutlineView
的委托,同时实现了 NSOutlineViewDelegate
和 NSTextFieldDelegate
协议。
另外,我也无法使用旧的 –outlineView:shouldEditTableColumn:item:
NSOutlineViewDelegate
方法,因为它仅适用于基于单元格的大纲视图(I'我假设情况是这样 - 大纲视图文档似乎没有针对 Lion 进行更新,尽管类似的 NSTableView 文档已经更新,并且这些方法也不会被调用) 。
更新
我在一个全新的测试项目中重现了这个,所以它绝对与我的任何自定义类都不相关。按照以下步骤创建我的示例项目,并重现此问题。
- 在 Xcode 4.1 中,创建一个 Mac OS X Cocoa Application 类型的新项目,不选择任何特殊选项
- 创建两个新文件,SourceListDataSource.m 和 SourceListDelegate.m,其中下面指定的内容
- 在 MainMenu.xib 中,将
Source List
拖到窗口上 - 将两个
Object
拖到 Dock(窗口左侧)上,指定其中一个为SourceListDataSource
类,另一个为SourceListDelegate
- 将大纲视图的
dataSource
和delegate
出口连接到这两个 打开 - 为大纲视图列内的 DataCell 视图选择静态文本
NSTextField
- 其
Value
绑定,保留默认设置 - 连接其
delegate
出口到来源列表委托对象 - 将其
Behavior
属性设置为 Editable - 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.
- In Xcode 4.1, create a new project, of type Mac OS X Cocoa Application, with no special options selected
- Create two new files, SourceListDataSource.m and SourceListDelegate.m, with the contents specified below
- In MainMenu.xib, drag a
Source List
onto the Window - Drag two
Object
s onto the dock (left side of the window), specifying theSourceListDataSource
class for one, and theSourceListDelegate
for the other - Connect the Outline View's
dataSource
anddelegate
outlets to those two objects - Select the Static Text
NSTextField
for the DataCell view inside the outline view's column - Turn on its
Value
binding, keeping the default settings - Connect its
delegate
outlet to the Source List Delegate object - Set its
Behavior
property to Editable - 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
子类
NSTableCellView
,带有文本字段的出口,并在awakeFromNib
中设置文本字段委托。完成此操作后,control:textShouldBeginEditing:
被调用。我不知道为什么,但是(编辑:)如果您在 xib 中设置委托,则不会调用委托方法 - 我和您有相同的经历。或者,您可以放弃委托,并使用绑定(到模型的布尔属性)有条件地设置可编辑,或者使用作用于模型实例并返回布尔值的值转换器。使用文本字段的可编辑绑定。
Subclass
NSTableCellView
, with an outlet for the text field, and set the text field delegate inawakeFromNib
. 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.
我也遇到过这个问题。因为我不想丢失绑定,所以我执行了以下操作:
将 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 theobjectValue
and set up a customNSValueTransformer
subclass.上面提出的其他解决方案性能不佳,并且不适用于现代版本的 macOS。当即将编辑一个文本字段时,
NSTableView
会在整个表格中的每个文本字段上调用 acceptsFirstResponder
。当您滚动表格时,第一响应者方法就会被调用。如果您在这些调用中添加一些日志记录,您将看到它们的运行情况。此外,不需要在 IB 之外的任何地方分配 textField 的委托,并且实际上不会起作用,因为
NSTableView
(因此NSOutlineView
)基本上“接管”它们包含的视图。正确的现代方法:
子类
NSTableView
(或NSOutlineView
)并执行以下操作:注意:
这是针对 macOS 11.3.1 和 Xcode 12.5 针对应用程序目标编写的macOS 11。
NSTableCellViews 中 NSTextFields 的
isEditable
属性必须设置为true
。 NSTableView 的 -validateFirstResponder 实现将首先检查该属性,因此您无需在委托方法中执行此操作。The other proposed solutions above are not performant and will not work on modern versions of macOS.
NSTableView
callsacceptsFirstResponder
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 thereforeNSOutlineView
) basically "take over" for the views they contain.The Correct, Modern Approach:
Subclass
NSTableView
(orNSOutlineView
) and do this:Notes:
This was written against macOS 11.3.1 and Xcode 12.5 for an application targeting macOS 11.
The
isEditable
property of the NSTextFields in your NSTableCellViews must be set totrue
. NSTableView's implementation of -validateFirstResponder will check that property first, so you need not do so in your delegate method.