NSTableView 和 NSOutlineView 在 tab 键上编辑

Posted

技术标签:

【中文标题】NSTableView 和 NSOutlineView 在 tab 键上编辑【英文标题】:NSTableView & NSOutlineView editing on tab key 【发布时间】:2011-08-01 19:54:12 【问题描述】:

我的应用程序有一个NSOutlineView 和一个NSTableView,我在这两个方面都遇到了同样的问题。选中任一行后,按 Tab 键可使第一列进入编辑模式,而不是让下一个键查看第一响应者。要进入下一个关键视图,您需要在所有列之间切换。

此外,切换到任一视图会导致 last 列进入编辑模式,需要更多的切换选项卡才能进入其先前的关键视图。

以防万一,我使用的是自动计算的键视图循环,而不是我自己的,我的 NSWindow 设置为 autorecalculatesKeyViewLoop = YES。一旦用户选择编辑列,我希望在列之间切换,但我不认为这是 trigger 编辑模式的 tab 键的标准行为。

更新

感谢以下有用的回复,我解决了。基本上,我在我的自定义表格视图类中覆盖-keyDown,它处理表格视图中的制表符和移位制表符。然而,解决表格视图中的 shift-tabbing 问题更加困难。如果它接受来自另一个视图的控制,我在自定义表视图的 -acceptsFirstResponder 中将布尔属性设置为 YES

当当前事件是 shift-tab keyDown 事件时,代理的 -tableView:shouldEditTableColumn:row 会检查这一点。 -tableView:shouldEditTableColumn:row 被调用并且它不是一个 shift-tab 事件,它将表格视图的属性设置回 NO 所以它仍然可以像往常一样编辑。

我在下面粘贴了完整的解决方案。

/* CustomTableView.h */

@interface CustomTableView : NSTableView 

@property (assign) BOOL justFocused;

@end

/* CustomTableView.m */

@implementation CustomTableView

@synthesize justFocused;

- (BOOL)acceptsFirstResponder 
    if ([[self window] firstResponder] != self) 
        justFocused = YES;
    

    return YES;


- (void)keyDown:(NSEvent *)theEvent

    // Handle the Tab key
    if ([[theEvent characters] characterAtIndex:0] == NSTabCharacter) 
        if (([theEvent modifierFlags] & NSShiftKeyMask) != NSShiftKeyMask) 
            [[self window] selectKeyViewFollowingView:self];
         else 
            [[self window] selectKeyViewPrecedingView:self];
        
    
    else 
        [super keyDown:theEvent];
    


@end

/* TableViewDelegate.m */

. . .

- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn
              row:(NSInteger)row

    NSEvent *event = [NSApp currentEvent];
    BOOL shiftTabbedIn = ([event type] == NSKeyDown
                          && [[event characters] characterAtIndex:0] == NSBackTabCharacter);

    if (shiftTabbedIn && ((CustomTableView *)tableView).justFocused == YES) 
        return NO;
     else 
        ((CustomTableView *)tableView).justFocused = NO;
    

    return YES;


. . .

【问题讨论】:

【参考方案1】:

这是默认行为。如果没有选择行,则整个表格视图具有焦点,并且 Tab 键切换到下一个键视图。如果选中了一行,表格视图将开始编辑,如果已经在编辑,则移动到下一个单元格。

来自AppKit Release Notes:

表格现在支持单元格间 导航如下:

向前跳到一个表格会聚焦整个表格。 Hitting Space 将尝试在 NSButtonCell 上执行“performClick:” 选定的行,如果只有一个 该行中的实例。 再次使用 Tab 键聚焦第一个“可聚焦”(1) 单元格(如果有的话)。 如果可以编辑新获得焦点的单元格,则将开始编辑。 Hitting Space 在单元格上调用“performClick:”并设置数据源 之后的值,如果更改。 (2) 如果正在编辑文本单元格,按 Enter 将提交编辑并获得焦点 将返回到 tableview,并且 Tab/Shift-tab 将提交编辑 然后执行新的标签循环 行为。 制表符只会在单行中制表符 到达一行中的最后一个单元格后,选项卡会将焦点移至 下一个可聚焦控件。 回退到表格中将选择最后一个可聚焦的单元格。

如果您想更改此行为,委托方法tableView:shouldEditTableColumn:row: 可能会有所帮助。如果您真的只想影响 Tab 键的行为,您可能还必须继承 NSTableView

【讨论】:

我在发布此问题之前尝试使用 tableView:shouldEditTableColumn:row:,但无法提出任何有效标准作为返回 NO 的基础 是的,如果你真的只想改变 Tab 的行为而不改变其他键,那么,就像我说的,你可能不得不子类化。不过,它应该相当轻松——您只需要覆盖keyDown: 并检查Tab。您刚刚传递给[super keyDown:event] 的任何其他密钥。 这是有道理的。另外,[event keyCode] == 48 是捕获 tab 键的最佳方法,还是这就是为什么 shift-tab 仍然是一个问题? 您可以查看事件的modifierFlags,看看是否按下了Shift。我不认为有更好的方法,不。 哦,我错过了你在下面的评论。【参考方案2】:

我以前也不得不处理这个问题。我的解决方案是继承 NSTableViewNSOutlineView 并覆盖 keyDown: 以捕捉那里的 tab 键按下,然后对其进行操作。

【讨论】:

我在发布之前尝试过这个,但它没有解决 shift-tab 场景,因为NSTableView 没有收到keyDown: 事件。你是怎么处理的?另外,[theEvent keyCode] == 48 是捕获 Tab 键的最佳方式,还是我的问题? @Dov 您应该能够获取事件字符并将其与NSTabCharacterNSBackTabCharacter 进行比较。【参考方案3】:

使用keyDown 的解决方案对我不起作用。也许是因为它是基于单元格的表格视图。

我在 Swift 中基于视图的表格视图的解决方案如下所示:

extension MyTableView: NSTextFieldDelegate 
    func controlTextDidEndEditing(_ obj: Notification) 
        guard
            let view = obj.object as? NSView,
            let textMovementInt = obj.userInfo?["NSTextMovement"] as? Int,
            let textMovement = NSTextMovement(rawValue: textMovementInt) else  return 

        let columnIndex = column(for: view)
        let rowIndex = row(for: view)

        let newRowIndex: Int
        switch textMovement 
        case .tab:
            newRowIndex = rowIndex + 1
            if newRowIndex >= numberOfRows  return 
        case .backtab:
            newRowIndex = rowIndex - 1
            if newRowIndex < 0  return 
        default: return
        

        DispatchQueue.main.async 
            self.editColumn(columnIndex, row: newRowIndex, with: nil, select: true)
        
    

您还需要设置cell.textField.delegate 以便实现工作。

我关于这个棘手解决方法的博文:https://samwize.com/2018/11/13/how-to-tab-to-next-row-in-nstableview-view-based-solution/

【讨论】:

【参考方案4】:

多么方便!昨天我自己也在看这个,很高兴看到我采用的方法得到了一些确认 - keyDown: 处理。

但是,我对您的方法有一个可能的小改进:我发现触发在 shift-tabbing 回到表格时进行编辑的方法是 becomeFirstResponder 调用。所以我对 NSTableView 子类所做的是:

    添加合成属性以控制是否禁用选项卡编辑行为 在 keydown 时,检查第一个字符(同时检查 [[theEvent characters] length] 以避免死键异常!)用于制表符;如果选项卡编辑被禁用,请根据您的代码示例转到下一个/上一个视图。 覆盖becomeFirstResponder
    - (BOOL)becomeFirstResponder 
        if (tabEditingDisabled) 
            【自我展示】;
            返回是;
        
        返回[超级成为FirstResponder];
    

这将所有代码保留在 tableview 子类中,保持委托更清洁 :)

唯一的危险是我不知道 NSTableView 在 becomeFirstResponder 中还有什么作用;我没有注意到任何损坏,但是...

【讨论】:

不过,在您的解决方案中,您何时更改tabEditingDisabled @Dov 因为我的 NSTableView 子类在少数地方使用,所以它只是一个标志,可以根据使用的位置切换行为。在我希望禁用选项卡编辑的几个地方,我将awakeFromNib 中的视图控制器更改为setTabEditingDisabled:NOtabEditingDisabled 不像代码中的 justFocused 那样是跟踪标志,更像是一种行为控制切换。【参考方案5】:

这对我有用:

- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row 
  NSEvent *e = [NSApp currentEvent];
  if (e.type == NSKeyDown && e.keyCode == 48) return NO;
  return YES;

【讨论】:

以上是关于NSTableView 和 NSOutlineView 在 tab 键上编辑的主要内容,如果未能解决你的问题,请参考以下文章

使用乘法 NSArrayControllers 和 NSTableView 绑定数据

NSTableView 和 NSOutlineView 在 tab 键上编辑

NSTableView 和 NSOutlineView 拖放

修改单元格后更新 NSTableView

NSTableView,多个单元格和绑定

放弃 NSTableView 行为