NSTableView & NSTableView Tab 键上的 NSOutlineView 编辑

发布于 2024-10-31 09:58:07 字数 2311 浏览 4 评论 0原文

我的应用程序有一个 NSOutlineView 和一个 NSTableView,我对两者都遇到了同样的问题。选择任一行后,按 Tab 键会将第一列置于编辑模式,而不是使下一个键查看第一响应者。要进入下一个关键视图,您需要按 Tab 键浏览所有列。

此外,按 Shift 键进入任一视图都会导致最后一列进入编辑模式,因此需要更多的 Shift 键才能进入其上一个关键视图。

如果重要的话,我正在使用自动计算的关键视图循环,而不是我自己的,并将我的 NSWindow 设置为 autorecalculatesKeyViewLoop = YES。一旦用户选择编辑列,我希望在列之间进行 Tab 键切换,但我不认为 Tab 键触发编辑模式是标准行为。

更新

感谢下面的有用回复,我解决了。基本上,我在自定义表视图类中重写 -keyDown ,该类处理表视图中的 Tab 键切换和Shift-Tab 键切换。然而,解决将 Shift-Tab 键切换到表视图的问题却更加困难。如果自定义表视图的 -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;
}

. . .

My app has an NSOutlineView and an NSTableView, and I'm having the same problem with both. With a row in either selected, pressing the tab key puts the first column into edit mode instead of making the next key view first responder. To get to the next key view, you need to tab through all of the columns.

Also, shift-tabbing into either view results in the last column going into edit mode, necessitating more shift-tabs to get into its previous key view.

In case it matters, I'm using the autocalculated key view loop, not my own, with my NSWindow set to autorecalculatesKeyViewLoop = YES. I would like tabbing between the columns once the user elects to edit a column, but I don't think it's standard behavior for the tab key to trigger edit mode.

Update

Thanks to the helpful responses below, I worked it out. Basically, I override -keyDown in my custom table view class, which handles tabbing and shift-tabbing out of the table view. It was tougher to solve shift-tabbing into the table view, however. I set a boolean property to YES in the custom table view's -acceptsFirstResponder if it's accepting control from another view.

The delegate's -tableView:shouldEditTableColumn:row checks for that when the current event is a shift-tab keyDown event. -tableView:shouldEditTableColumn:row is called and it's not a shift-tab event, it sets the table view's property back to NO so it can still be edited as usual.

I've pasted the full solution below.

/* 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;
}

. . .

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

裸钻 2024-11-07 09:58:07

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

来自 AppKit 发行说明

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

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

如果您想更改此行为,委托方法 tableView:shouldEditTableColumn:row: 可能会有所帮助。如果您确实只想影响 Tab 键的行为,您可能还必须对 NSTableView 进行子类化。

This is the default behavior. If there's no row selected, the table view as a whole has focus, and the Tab key switches to the next key view. If there is a row selected, the table view begins editing or moves to the next cell if already editing.

From AppKit Release Notes:

Tables now support inter-cell
navigation as follows:

  • Tabbing forward to a table focuses the entire table.
  • Hitting Space will attempt to 'performClick:' on a NSButtonCell in
    the selected row, if there is only one
    instance in that row.
  • Tabbing again focuses the first "focusable" (1) cell, if there is one.
  • If the newly focused cell can be edited, editing will begin.
  • Hitting Space calls 'performClick:' on the cell and sets the datasource
    value afterwards, if changed. (2)
  • If a text cell is editing, hitting Enter will commit editing and focus
    will be returned to the tableview, and
    Tab/Shift-tab will commit the editing
    and then perform the new tab-loop
    behavior.
  • Tabbing will only tab through a single row
  • Once the last cell in a row is reached, tab will take the focus to
    the next focusable control.
  • Back tabbing into a table will select the last focusable cell.

If you want to change this behavior, the delegate method tableView:shouldEditTableColumn:row: may be helpful. You may also have to subclass NSTableView if you really want to affect only the behavior of the Tab key.

瀞厅☆埖开 2024-11-07 09:58:07

使用 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/

The solution using keyDown didn't work for me. Perhaps because it is for cell-based table view.

My solution for a view-based table view, in Swift, looks like this:

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)
        }
    }
}

You also need to set the cell.textField.delegate so that the implementation works.

My blog post on this tricky workaround: https://samwize.com/2018/11/13/how-to-tab-to-next-row-in-nstableview-view-based-solution/

梦里梦着梦中梦 2024-11-07 09:58:07

我以前也必须处理过这个问题。我的解决方案是子类 NSTableViewNSOutlineView 并重写 keyDown: 以捕获那里的 Tab 键按下,然后对其进行操作。

I've had to deal with this before as well. My solution was to subclass NSTableView or NSOutlineView and override keyDown: to catch the tab key presses there, then act on them.

小嗷兮 2024-11-07 09:58:07

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

不过,我对您的方法有一个可能的小改进:我发现,在按 Shift-Tab 键返回表格时触发编辑的方法是 becomeFirstResponder 调用。所以我在 NSTableView 子类上所做的是:

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

这将所有代码保留在 tableview 子类中,从而使委托保持干净:)

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

How convenient! I was just looking at this myself yesterday, and it's good to see some confirmation of the approach I took - keyDown: handling.

However, I have one small possible refinement to your approach: I worked out that the method triggering editing on shift-tabbing back to the table was the becomeFirstResponder call. So what I did on a NSTableView subclass was:

  1. Add a synthesized property to control whether tab-editing behaviour was disabled
  2. On keydown, check the first character (also check for [[theEvent characters] length] to avoid exceptions for dead keys!) for tab; if tab editing is disabled, move on to the next/previous view, as per your code sample.
  3. Override becomeFirstResponder:
    - (BOOL)becomeFirstResponder {
        if (tabEditingDisabled) {
            [self display];
            return YES;
        }
        return [super becomeFirstResponder];
    }

This keeps all the code in the tableview subclass, keeping the delegate cleaner :)

The only danger is I don't know what else NSTableView does in becomeFirstResponder; I didn't notice anything breaking, but...

空城缀染半城烟沙 2024-11-07 09:58:07

这对我有用:

- (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;
}

This worked for me:

- (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;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文