基于视图的 NSTableView 编辑

View based NSTableView editing

我不确定我做错了什么。由于我找不到关于此的任何其他问题(甚至文档),它似乎对其他人来说正常工作没有问题。

我只是想获得一个基于 NSTableView 的视图来支持对其内容的编辑。 IE。该应用程序显示一个包含一列和多行的 NSTableView,其中包含一个带有一些内容的 NSTextField。我希望能够(双击)单击一个单元格并编辑该单元格的内容。所以基本上是基于单元格的 NSTableView 的正常行为,其中实现了 tableView:setObjectValue:forTableColumn:row: 方法。

我在 Apple 的 TableViewPlayground 示例代码中分析了 Complex TableView 示例(它支持单元格内容的编辑),但是我找不到启用编辑的setting/code/switch。

这是一个简单的示例项目(Xcode 6.1.1,SDK 10.10,基于故事板):

Header:

#import <Cocoa/Cocoa.h>

@interface ViewController : NSViewController

@property (weak) IBOutlet NSTableView *tableView;

@end

实施:

#import "ViewController.h"

@implementation ViewController
{
    NSMutableArray* _content;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    _content = [NSMutableArray array];

    for(NSInteger i = 0; i<10; i++) {
        [_content addObject:[NSString stringWithFormat:@"Item %ld", i]];
    }
}

#pragma mark - NSTableViewDataSource
- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
{
    return _content.count;
}

#pragma mark - NSTableViewDelegate
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
    NSTableCellView* cell = [tableView makeViewWithIdentifier:@"CellView" owner:self];
    cell.textField.stringValue = _content[row];
    return cell;
}

- (IBAction)endEditingText:(id)sender {
    NSInteger row = [_tableView rowForView:sender];
    if (row != -1) {
        _content[row] = [sender stringValue];
    }
}
@end

故事板文件如下所示:

table 视图的数据源和委托设置为视图控制器。 运行此应用程序时,table视图显示 10 个测试行,但无法编辑其中一行。

这是为什么?我在这里错过了什么?

我仔细检查了 NSTableView 的所有属性(及其内容)是否与 Apple 的 TableViewPlayground 示例中的相同。在搜索文档和互联网几个小时以寻找有用的提示但没有成功之后,我有点沮丧。您在基于视图的 NSTableView 上所能找到的所有内容都是 non-editable 示例或关于 editable 内容的非常模糊的信息。当然,还有大量关于 editable、基于单元格的 NSTableViews...

的信息、文档和示例

我的示例项目的 zip 可以在这里下载: TableTest.zip

默认情况下,每个单元格(NSTableCellView 的实例)上都有一个 NSTextField。当您编辑单元格时,您实际上正在编辑的是这个文本字段。 Interface Builder 使此文本字段不可编辑:

您需要做的就是将 行为 弹出窗口设置为 Editable。现在,您可以通过 return 命中或 单次 单击来编辑文本字段。

尽管所有用于编辑基于 view 的 NSTableView 的部分都出现在问题和答案中,但我仍然无法将它们放在一起。下面的演示是在Swift,使用Xcode 6.3.2,但是对于objective-C cavemen/womens应该很容易理解。完整的代码清单在最后。

让我们从这里开始:

NSTableViewDataSource Protocol Reference

Setting Values

- tableView:setObjectValue:forTableColumn:row:

Swift:  
optional func tableView(_ aTableView: NSTableView,
              setObjectValue anObject: AnyObject?,
              forTableColumn aTableColumn: NSTableColumn?,
              row rowIndex: Int)

Objective-C:
- (void)tableView:(NSTableView *)aTableView
   setObjectValue:(id)anObject
   forTableColumn:(NSTableColumn *)aTableColumn
              row:(NSInteger)rowIndex

Discussion: This method is intended for use with cell-based table views, it must not be used with view-based table views. In view-based tables, use target/action to set each item in the view cell.

如果您像我一样,您搜索了 NSTableViewDelegate 和 NSTableViewDataSource 协议以寻找某种编辑方法来使用。然而,上面引用的讨论告诉你事情要简单得多。

1) 如果您在 文档大纲 中查看 Xcode 中的 TableView,您会看到类似这个:

TableView 中的单元格由 Table Cell View 表示。该单元格包含一些项目,默认情况下其中一项是 NSTextField。但是文档大纲中的 NSTextField 在哪里?!嗯,文档大纲中的 controls 在其名称旁边有一个看起来像滑块的图标。看一看。在单元格内,您会看到旁边有一个滑块图标的东西。而且,如果您 select 文档大纲中的那一行,然后打开身份检查器,您会看到它是一个 NSTextField:

您可以认为它只是一个普通的旧 NSTextField。

当您实现 NSTableViewDataSource 协议方法时:

import Cocoa

class MainWindowController: NSWindowController,
                            NSTableViewDataSource {
    ...
    ...
    var items: [String] = []  //The data source: an array of String's
    ...
    ...

    // MARK: NSTableViewDataSource protocol methods

    func numberOfRowsInTableView(tableView: NSTableView) -> Int {
        return items.count
    }

    func tableView(tableView: NSTableView,
                   objectValueForTableColumn tableColumn: NSTableColumn?,
                   row: Int) -> AnyObject? {
        return items[row]
    }

}

..TableView 获取第二个方法返回的值并将其分配给外部 Table Cell View 中名为 objectValue 的 属性——换句话说,TableView 不使用返回值设置 NSTextField(即内部 Table View Cell)。这意味着您的数据源项目将不会显示在 TableView 中,因为 NSTextField 是显示项目的地方。为了设置 NSTextField 的值,您需要将 NSTextField 的 value 连接或 绑定 到 objectValue 属性。您在绑定检查器中执行此操作:

Warning: Make sure you don't check the Bind to checkbox until after you select the object you want to bind to. If you check the checkbox first, an object will be inserted into your document outline, and if you don't notice it, you will get errors when you run your program. If you accidentally check the Bind to checkbox first, make sure you delete the automatically added object in your document outline. Xcode creates a separate section for the added object, so it is easy to spot in your document outline.

2) 回溯一下,您可能熟悉将按钮连接到 action 方法,此后如果您单击在按钮上,action 方法将执行。另一方面,对于 NSTextField,您通常声明一个 IBOutlet,然后使用它来获取或设置 NSTextField 的 stringValue.

但是,NSTextField 也可以触发 action 方法的执行。世界卫生大会??!但是你不能像点击按钮那样点击 NSTextfield!尽管如此,NSTextField 有一个触发器,就像单击按钮一样,这将导致执行 action 方法,触发器是: done editing the NSTextField。 NSTextField 如何知道您何时完成编辑?有两种方式:

  1. 你命中了Return.

  2. 你点击了其他控件。

您可以在属性检查器中选择触发器:

3) 正如@Paul Patterson 在他的回答中所展示的,接下来您需要做的是将 NSTextField 的 Behavior 设置为 Editable 在属性检查器中。

4) 然后将 NSTextField 连接到您要执行的操作方法。如果您还没有使用以下技巧将控件连接到操作方法,您应该尝试一下:

Select your .xib file in the Project Navigator, so that the window and its controls are displayed. Then click on the Assistant Editor(the two_interlocking_rings icon at the top of the Xcode window on the far right)--that will display your Controller file(if some other file is shown, then use the jump bar to navigate to your Controller file). Then Control+drag from the NSTextField (in the document outline) to the spot in your Controller file where you want to create your action method:

当你释放时,你会看到这个弹出窗口 window:

如果输入与所示相同的信息,将在文件中输入以下代码:

@IBAction func onEnterInTextField(sender: NSTextField) {

}

并且...NSTextField 和操作方法之间的连接已经建立。 (您也可以使用这些步骤来创建和连接 IBOutlet。)

5) 在 action 方法中,您可以从 TableView 中获取当前 selected 行,即刚刚编辑过的行:

@IBAction func onEnterInTextField(sender: NSTextField) {    
    let selectedRowNumber = tableView.selectedRow  //tableView is an IBOutlet connected to the NSTableView

}

然后我对如何在 TableView 中获取 selected 行的文本感到困惑,然后回到我匆忙查看 TableView 和协议方法的文档。但是,我们都知道如何获取 NSTextField 的字符串值,对吧? 发件人 是您正在编辑的 NSTextField:

@IBAction func onEnterInTextField(sender: NSTextField) {
    let selectedRowNumber = tableView.selectedRow  //My Controller has  an IBOutlet property named tableView which is connected to the TableView 

    if selectedRowNumber != -1 {  //-1 is returned when no row is selected in the TableView
        items[selectedRowNumber] = sender.stringValue  //items is the data source, which is an array of Strings to be displayed in the TableView
    }

}

如果您不在数据源中插入新值,那么下次 TableView 需要显示行时,原始值将再次显示,覆盖已编辑的更改。请记住,TableView 从数据源而不是 NSTextField 中检索值。然后 NSTextField 显示 TableView 分配给单元格的 objectValue 属性.

的任何值

最后一件事:我收到一条警告,说我无法将 NSTextField 连接到 class 中的操作,如果 class '不是 TableView 的代表....所以我连接了 TableView 的代表 ou让给文件所有者:

我之前将文件的所有者设置为我的控制器(=MainWindowController),所以在我建立连接后,MainWindowController——包含 NSTextField 的操作方法——成为 TableView 的委托,并且警告消失了。

随机提示:

1) 我发现 开始 编辑 NSTextField 的最简单方法是 select TableView 中的一行,然后点击 Return。

2) NSTableView 默认有两列。如果您 select 文档大纲中的其中一列,然后点击键盘上的 Delete,您可以制作一列 table--但是 TableView 仍然显示列分隔符,所以它看起来像仍然是两列。要去掉列分隔符,select 文档大纲中的 Bordered Scroll View - Table View,然后拖动其中一个角来调整 TableView 的大小——单个列会立即调整自身大小以占据所有可用的 space。

感谢步骤 #1 和 #2,以及随机提示 #2:Cocoa Programming For OS X(第 5 版,2015 年)。

完整代码清单:

//
//  MainWindowController.swift
//  ToDo
//

//import Foundation
import Cocoa

class MainWindowController: NSWindowController,
                            NSTableViewDataSource {

    //@IBOutlet var window: NSWindow? -- inherited from NSWindowController
    @IBOutlet weak var textField: NSTextField!
    @IBOutlet weak var tableView: NSTableView!

    var items: [String] = []  //The data source: an array of String's

    override var windowNibName: String {
        return "MainWindow"
    }

    @IBAction func onclickAddButton(sender: NSButton) {
        items.append(textField.stringValue)
        tableView.reloadData()  //Displays the new item in the TableView

    }

    @IBAction func onEnterInTextField(sender: NSTextField) {
        let selectedRowNumber = tableView.selectedRow

        if selectedRowNumber != -1 {
            items[selectedRowNumber] = sender.stringValue
        }
    }


    // MARK: NSTableViewDataSource protocol methods

    func numberOfRowsInTableView(tableView: NSTableView) -> Int {
        return items.count
    }

    func tableView(tableView: NSTableView,
                   objectValueForTableColumn tableColumn: NSTableColumn?,
                   row: Int) -> AnyObject? {
        return items[row]
    }

 }

显示文件所有者(= MainWindowController)的所有连接的连接检查器:

只是对已接受答案的更正 - 您应该使用 tableView.row(for: NSView) 和 tableView.column(for: NSView) 获取行和列。其他方法可能不可靠。

@IBAction func textEdited(_ sender: Any) {
        if let textField = sender as? NSTextField {

            let row = self.tableView.row(for: sender as! NSView)
            let col = self.tableView.column(for: sender as! NSView)
            self.data[row][col] = textField.stringValue

            print("\(row), \(col), \(textField.stringValue)")

            print("\(data)")
        }
    }