我有一个主-细节类型的应用程序OS X 10.9:
一个NSTableView在窗口的左侧,详细信息作为NSTextFields在右侧。
我没有特别设置标签顺序,开箱即用就可以了。
插入新对象后除外:
- 插入新对象后,对象在NSTableView中被选中。 <
- 点击选项卡/gh>
- 第一个NSTextField被选中
- 写点东西,点击tab
- 现在不是选择下一个NSTextField 没有被选中, <
- 点击选项卡/gh>
- NSTableView被选中 <
- 点击选项卡/gh>
- 第一个NSTextField被选中
我想要什么:
- 插入新对象后,应选择detail部分的第一个NSTextField <
- 点击选项卡/gh>
- detail部分的下一个NSTextField应该被选中
- …
更新:我发现奇怪的行为是通过在界面生成器中启用NSArrayController的"自动重新安排内容"触发的。如果我禁用它,我就不会再失去注意力了。参见coredata绑定的NSTableView在项目改变时失去输入焦点,但只在排序时。
但是,当然,我仍然希望我的内容被自动排序-只是在操作过程中不会失去我的焦点。这能做到吗?
UPDATE2:我遵循了@KenThomases的建议,并记录了堆栈跟踪的时刻,当我的文本字段失去键盘焦点:
2014-08-17 16:30:26.003 ResponderExperiment[712:303] (
0 ResponderExperiment 0x00000001000014dd -[WMWindow makeFirstResponder:] + 61
1 AppKit 0x00007fff8d25c778 -[NSTextView(NSPrivate) _giveUpFirstResponder:] + 257
2 AppKit 0x00007fff8d25c56c -[NSTextView(NSKeyBindingCommands) insertTab:] + 270
3 AppKit 0x00007fff8d22dc2f -[NSResponder doCommandBySelector:] + 71
4 AppKit 0x00007fff8d25b10a -[NSTextView doCommandBySelector:] + 196
5 AppKit 0x00007fff8d22d151 -[NSKeyBindingManager(NSKeyBindingManager_MultiClients) interpretEventAsCommand:forClient:] + 1392
6 AppKit 0x00007fff8d24c1c2 -[NSTextInputContext handleEvent:] + 845
7 AppKit 0x00007fff8d22b9dd -[NSView interpretKeyEvents:] + 180
8 AppKit 0x00007fff8d24bd6d -[NSTextView keyDown:] + 658
9 AppKit 0x00007fff8d1f856b -[NSWindow sendEvent:] + 1843
10 AppKit 0x00007fff8d199b32 -[NSApplication sendEvent:] + 3395
11 AppKit 0x00007fff8cfe99f9 -[NSApplication run] + 646
12 AppKit 0x00007fff8cfd4783 NSApplicationMain + 940
13 ResponderExperiment 0x0000000100002de2 main + 34
14 libdyld.dylib 0x00007fff8c5bf5fd start + 1
15 ??? 0x0000000000000003 0x0 + 3
)
2014-08-17 16:30:26.014 ResponderExperiment[712:303] (
0 ResponderExperiment 0x00000001000014dd -[WMWindow makeFirstResponder:] + 61
1 AppKit 0x00007fff8d03c918 -[NSControl abortEditing] + 83
2 AppKit 0x00007fff8d2723e0 -[NSValueBinder discardEditing] + 162
3 AppKit 0x00007fff8d1aee30 -[NSController discardEditing] + 115
4 AppKit 0x00007fff8d1af971 -[NSArrayController rearrangeObjects] + 27
5 AppKit 0x00007fff8d3697a9 -[NSArrayController observeValueForKeyPath:ofObject:change:context:] + 294
6 AppKit 0x00007fff8d39ac08 -[NSArrayController _setMultipleValue:forKeyPath:atIndex:] + 323
7 AppKit 0x00007fff8d39ba12 -[NSArrayController _setSingleValue:forKeyPath:] + 137
8 Foundation 0x00007fff8bf9e19a -[NSObject(NSKeyValueCoding) setValue:forKeyPath:] + 285
9 AppKit 0x00007fff8d25444c -[NSBinder _setValue:forKeyPath:ofObject:mode:validateImmediately:raisesForNotApplicableKeys:error:] + 364
10 AppKit 0x00007fff8d254287 -[NSBinder setValue:forBinding:error:] + 245
11 AppKit 0x00007fff8d74c4ee -[NSValueBinder _applyObjectValue:forBinding:canRecoverFromErrors:handleErrors:typeOfAlert:discardEditingCallback:otherCallback:callbackContextInfo:didRunAlert:] + 194
12 AppKit 0x00007fff8d74c871 -[NSValueBinder applyDisplayedValueHandleErrors:typeOfAlert:canRecoverFromErrors:discardEditingCallback:otherCallback:callbackContextInfo:didRunAlert:error:] + 621
13 AppKit 0x00007fff8d74c9d4 -[NSValueBinder _applyDisplayedValueIfHasUncommittedChangesWithHandleErrors:typeOfAlert:discardEditingCallback:otherCallback:callbackContextInfo:didRunAlert:error:] + 127
14 AppKit 0x00007fff8d253c2e -[NSValueBinder validateAndCommitValueInEditor:editingIsEnding:errorUserInterfaceHandled:] + 436
15 AppKit 0x00007fff8d253a57 -[_NSBindingAdaptor _validateAndCommitValueInEditor:editingIsEnding:errorUserInterfaceHandled:bindingAdaptor:] + 160
16 AppKit 0x00007fff8d25399d -[_NSBindingAdaptor validateAndCommitValueInEditor:editingIsEnding:errorUserInterfaceHandled:] + 260
17 AppKit 0x00007fff8d25e0ba -[NSTextField textShouldEndEditing:] + 402
18 AppKit 0x00007fff8d25dc63 -[NSTextView(NSSharing) resignFirstResponder] + 393
19 AppKit 0x00007fff8d136170 -[NSWindow makeFirstResponder:] + 455
20 ResponderExperiment 0x000000010000154c -[WMWindow makeFirstResponder:] + 172
21 AppKit 0x00007fff8d25c778 -[NSTextView(NSPrivate) _giveUpFirstResponder:] + 257
22 AppKit 0x00007fff8d25c56c -[NSTextView(NSKeyBindingCommands) insertTab:] + 270
23 AppKit 0x00007fff8d22dc2f -[NSResponder doCommandBySelector:] + 71
24 AppKit 0x00007fff8d25b10a -[NSTextView doCommandBySelector:] + 196
25 AppKit 0x00007fff8d22d151 -[NSKeyBindingManager(NSKeyBindingManager_MultiClients) interpretEventAsCommand:forClient:] + 1392
26 AppKit 0x00007fff8d24c1c2 -[NSTextInputContext handleEvent:] + 845
27 AppKit 0x00007fff8d22b9dd -[NSView interpretKeyEvents:] + 180
28 AppKit 0x00007fff8d24bd6d -[NSTextView keyDown:] + 658
29 AppKit 0x00007fff8d1f856b -[NSWindow sendEvent:] + 1843
30 AppKit 0x00007fff8d199b32 -[NSApplication sendEvent:] + 3395
31 AppKit 0x00007fff8cfe99f9 -[NSApplication run] + 646
32 AppKit 0x00007fff8cfd4783 NSApplicationMain + 940
33 ResponderExperiment 0x0000000100002de2 main + 34
34 libdyld.dylib 0x00007fff8c5bf5fd start + 1
35 ??? 0x0000000000000003 0x0 + 3
)
也许我可以写我自己的NSArrayController rearrangeObjects:方法,那不会失去焦点?
这是一个可能的解决方案。其思想是将文本字段(或者更准确地说,是提供文本字段的字段编辑器)对Tab键的响应作为两个单独的步骤重新实现。首先,结束编辑,这将使数组控制器重新排列其内容。我们通过重置焦点来结束编辑,所以我们不关心数组控制器可能会对焦点做什么。然后,把焦点从原来聚焦的文本字段移开。
在文本字段上设置委托。在该委托中,实现以下方法:
- (BOOL) control:(NSControl*)control textView:(NSTextView*)textView doCommandBySelector:(SEL)command
{
if (command == @selector(insertTab:))
{
// End editing. This will commit the edited value and cause the array controller
// to rearranged its contents.
[control.window makeFirstResponder:nil];
// This will put focus on the next control from the text field.
[control.window selectKeyViewFollowingView:control];
// Prevent the normal processing of this command
return YES;
}
if (command == @selector(insertBacktab:))
{
// End editing. This will commit the edited value and cause the array controller
// to rearranged its contents.
[control.window makeFirstResponder:nil];
// This will put focus on the previous control from the text field.
[control.window selectKeyViewPrecedingView:control];
// Prevent the normal processing of this command
return YES;
}
return NO;
}
有一件事我不确定:如果不是从文本字段中单击tab,而是单击另一个文本字段会发生什么。在这种情况下,阵列控制器是否仍然重置焦点?
在我的例子中,我用我自己的逻辑重写了NSControl的abortEditing方法。
- (BOOL)abortEditing {
MyObject *object = [myArrayController.arrangedObjects objectAtIndex:self.editedRow];
return ![[Manager sharedInstance] isObjectBeingEditing:object];
}