NSTableView列标题的双击编辑

9
有没有可能通过双击列标题来更改NSTableView列的名称?有关如何实现此操作的最佳建议。
我正在尝试:
1.将表视图的双击操作设置为在双击时调用自定义方法
2.尝试通过调用editWithFrame:inView:editor:delegate:event:来编辑NSTableHeaderCell实例。
但我不确定为什么这会扭曲文本,当您双击标题时,它会使文本看起来像这样,并且没有字段编辑器出现,
在AppDelegate中,
-(void)awakeFromNib
{
    ...
    [_tableView setDoubleAction:@selector(doubleClickInTableView:)];
    ...
}

-(void) doubleClickInTableView:(id)sender
{
    NSInteger row = [_tableView clickedRow];
    NSInteger column = [_tableView clickedColumn];
    if(row == -1){
        /* Want to edit the column header on double-click */
        NSTableColumn *tableColumn = [[_tableView tableColumns] objectAtIndex:column];
        NSTableHeaderView *headerView = [_tableView headerView];
        NSTableHeaderCell *headerCell = [tableColumn headerCell];
        NSRect cellFrame = [headerView headerRectOfColumn:column];
        NSText * fieldEditor = [[headerView window] fieldEditor:YES forObject:nil];
        [headerCell editWithFrame:cellFrame inView:headerView editor:fieldEditor delegate:headerCell event:nil];
    }

}
4个回答

8

看起来是可行的。

在您的截图中,窗口的字段编辑器覆盖了您的单元格文本字段。编辑器具有透明背景,这就是为什么它混乱的原因。

所以这里是解决方案:

您需要拥有自己的NSTableHeaderCell子类来充当字段编辑器的委托:

@interface NBETableHeaderCell () <NSTextViewDelegate>
@end

@implementation NBETableHeaderCell

- (void)textDidEndEditing:(NSNotification *)notification
{
    NSTextView *editor = notification.object;
    // Update the title, kill the focus ring, end editing
    [self setTitle:editor.string];
    [self setHighlighted:NO];
    [self endEditing:editor];
}

- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
{
    if([self isHighlighted])
    {
        [self drawFocusRingMaskWithFrame:cellFrame inView:controlView.superview];
    }

    [super drawWithFrame:cellFrame inView:controlView];
}

- (void)drawFocusRingMaskWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
{
    [controlView lockFocus];
    NSSetFocusRingStyle(NSFocusRingOnly);
    [[NSBezierPath bezierPathWithRect:cellFrame] fill];
    [controlView unlockFocus];
}

@end

在应用程序代理的awakeFromNib方法中,不要忘记将NSTableHeaderCell设置为可编辑!
- (void)awakeFromNib
{
    NSTableColumn *newCol = [[NSTableColumn alloc] initWithIdentifier:@"whatever"];

    NBETableHeaderCell *hc = [[NBETableHeaderCell alloc] initTextCell:@"Default header text"];
    [hc setEditable:YES];
    [hc setUsesSingleLineMode:YES];
    [hc setScrollable:NO];
    [hc setLineBreakMode:NSLineBreakByTruncatingTail];
    [newCol setHeaderCell:hc];

    [self.tableView addTableColumn:newCol];
    [self.tableView setDoubleAction:@selector(doubleClickInTableView:)];
}

对于其余的部分,你已经接近成功了
调用selectWithFrame之后,我们自定义编辑器,使其具有漂亮的白色不透明背景,
以便我们不会看到它下面的文本视图
至于焦点环:这是单元格的工作,
我们只需将单元格设置为高亮状态,以便它知道现在必须绘制环

- (void)doubleClickInTableView:(id)sender
{
    NSInteger row = [_tableView clickedRow];
    NSInteger column = [_tableView clickedColumn];

    if(row == -1&& column >= 0)
    {
        NSTableColumn *tableColumn = [[_tableView tableColumns] objectAtIndex:column];
        NSTableHeaderView *headerView = [_tableView headerView];
        NBETableHeaderCell *headerCell = [tableColumn headerCell];

        // cellEditor is basically a unique NSTextView shared by the window
        // that adjusts its style to the field calling him
        // it stands above the text field's view giving the illusion that you are editing it
        // and if it has no background you will see the editor's NSTextView overlaying the TextField
        // wich is why you have that nasty bold text effect in your screenshot
        id cellEditor = [self.window fieldEditor:YES forObject:self.tableView];

        [headerCell setHighlighted:YES];
        [headerCell selectWithFrame:[headerView headerRectOfColumn:column]
                             inView:headerView
                             editor:cellEditor
                           delegate:headerCell
                              start:0
                             length:headerCell.stringValue.length];

        [cellEditor setBackgroundColor:[NSColor whiteColor]];
        [cellEditor setDrawsBackground:YES];
    }
}

在此处有关字段编辑器的更多信息:http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/WinPanel/Tasks/UsingWindowFieldEditor.html

现在唯一缺少的就是如果您在编辑时调整单元格大小,则字段编辑器的框架不会更新…


非常感谢您提供如此优雅的解决方案! - Daniel Farrell
哎呀,第一次悬赏任务,我没意识到我需要按下50+按钮! - Daniel Farrell

3
这也困扰着我,因此我创建了一个示例项目并将其上传到GitHub。(点击此处
如@ben-rhayader所指出的那样,重点在于将字段编辑器放置在标题视图单元格上方。
双击处理
以下是我们已经知道的有关Swift的信息。
窗口控制器/自定义表格视图控制器
视图控制器中有趣的部分是双击编辑标题。要实现这一点:
- 将TableWindowController(或您的视图控制器)实例作为对象放入Nib中, - 添加@IBAction func tableViewDoubleClick(sender: NSTableView)或类似方法, - 将NSTableView的doubleAction方法连接到tableViewDoubleClick。
编辑单元格很简单。编辑列标题则不太容易。
- 标题行的行值为-1, - 要定位字段编辑器,您需要列标题框架和字段编辑器本身。
结果的一部分:
extension TableWindowController {

    @IBAction func tableViewDoubleClick(sender: NSTableView) {

        let column = sender.clickedColumn
        let row = sender.clickedRow

        guard column > -1 else { return }

        if row == -1 {
            editColumnHeader(tableView: sender, column: column)
            return
        }

        editCell(tableView: sender, column: column, row: row)
    }

    private func editColumnHeader(tableView tableView: NSTableView, column: Int) {

        guard column > -1,
            let tableColumn = tableView.tableColumn(column: column),
            headerView = tableView.headerView as? TableHeaderView,
            headerCell = tableColumn.headerCell as? TableHeaderCell,
            fieldEditor = fieldEditor(object: headerView)
            else { return }

        headerCell.edit(
            fieldEditor: fieldEditor,
            frame: headerView.paddedHeaderRect(column: column),
            headerView: headerView)
    }

    private func editCell(tableView tableView: NSTableView, column: Int, row: Int) {

        guard row > -1 && column > -1,
            let view = tableView.viewAtColumn(column, row: row, makeIfNecessary: true) as? NSTableCellView
            else { return }

        view.textField?.selectText(self)
    }

    /// Convenience accessor to the `window`s field editor.
    func fieldEditor(object object: AnyObject?) -> NSText? {
        return self.window?.fieldEditor(true, forObject: object)
    }
}

自定义表头视图和表头视图单元格

正确定位字段编辑器需要一些工作。我将其放入了一个NSTableHeaderView子类中:

class TableHeaderView: NSTableHeaderView {

    /// Trial and error result of the text frame that fits.
    struct Padding {
        static let Vertical: CGFloat = 4
        static let Right: CGFloat = 1
    }

    /// By default, the field editor will be very high and thus look weird.
    /// This scales the header rect down a bit so the field editor is put
    /// truly in place.
    func paddedHeaderRect(column column: Int) -> NSRect {

        let paddedVertical = CGRectInset(self.headerRectOfColumn(column), 0, Padding.Vertical)
        let paddedRight = CGRect(
            origin: paddedVertical.origin,
            size: CGSize(width: paddedVertical.width - Padding.Right, height: paddedVertical.height))

        return paddedRight
    }
}

现在定位字段编辑器已经处理完毕。现在从上面的双击处理程序中使用它:

class TableHeaderCell: NSTableHeaderCell, NSTextViewDelegate {

    func edit(fieldEditor fieldEditor: NSText, frame: NSRect, headerView: NSView) {

        let endOfText = (self.stringValue as NSString).length
        self.highlighted = true
        self.selectWithFrame(frame,
            inView: headerView,
            editor: fieldEditor,
            delegate: self,
            start: endOfText,
            length: 0)

        fieldEditor.backgroundColor = NSColor.whiteColor()
        fieldEditor.drawsBackground = true
    }

    func textDidEndEditing(notification: NSNotification) {

        guard let editor = notification.object as? NSText else { return }

        self.title = editor.string ?? ""
        self.highlighted = false
        self.endEditing(editor)
    }
}

如何在用户双击其他标题单元格时“结束编辑”?

问题:当用户双击其他标题单元格时,字段编辑器将被重用并仅被重新定位。 textDidEndEditing 不会被调用。新值将不会被保存。

@triple.s 和 @boyfarrell 讨论过此问题,但没有提供代码 -- 我发现最简单的方法是劫持字段编辑器的构造并手动调用 endEditing 以知道何时字段编辑器将更改。

class HeaderFieldEditor: NSTextView {

    func switchEditingTarget() {

        guard let cell = self.delegate as? NSCell else { return }

        cell.endEditing(self)
    }
}

必要时使用此自定义字段编辑器:

class TableWindowController: NSWindowDelegate {

    func windowWillReturnFieldEditor(sender: NSWindow, toObject client: AnyObject?) -> AnyObject? {

        // Return default field editor for everything not in the header.
        guard client is TableHeaderView else { return nil }

        // Comment out this line to see what happens by default: the old header
        // is not deselected.
        headerFieldEditor.switchEditingTarget()

        return headerFieldEditor
    }

    lazy var headerFieldEditor: HeaderFieldEditor = {
        let editor = HeaderFieldEditor()
        editor.fieldEditor = true
        return editor
    }()
}

非常好用。

Github上的项目链接:https://github.com/DivineDominion/Editable-NSTableView-Header


1

@BenRhayader -- 只有在我更改列标题文本并进行标签输出以调用controlTextDidEndEditing委托时,此解决方案才有效。但是,如果我更改一个列的标题列文本并单击其他列(而不是进行标签输出),则保留旧文本,即新文本不反映。这可能是因为更改文本的逻辑写在controlTextDidEndEditing中,该方法仅在执行标签输出时调用。


没错,我也注意到了这个问题。我用了一种稍微hack的方法来解决它。我在NSTableHeaderCell的子类中添加了一个实例变量,当文本被编辑时保存字段编辑器实例。如果我检测到焦点的改变(例如,像你建议的那样按Tab键),那么我可以通过在单元格上调用-endEditing:并传递保存的字段编辑器来结束编辑。我想这不算太hack,但可能还有改进的空间。 - Daniel Farrell
@BenRhayader - 你好,我添加了一个实例变量NSTextView *myEditor;并在controlTextDidEndEditing中设置了它的初始值。现在,如果我更改标题列(而不是按Tab键),我应该在哪里设置这个值,以便文本会反映在列标题中。 - triandicAnt
没错,那很好。但我现在不太确定我当时是如何解决这个问题的。已经有一段时间了。我需要检查我的代码,我会把它放到Github上,但我没有太多时间,可能需要一周左右... - Daniel Farrell
问题已在另一个答案中解决,请查看以下内容。(或者以上。这取决于具体情况 :)) - ctietze

0

谢谢,我之前的搜索中没有找到这个。基本上,我想要的功能与使用界面构建器命名列标题时相同;只需双击标题并命名即可! - Daniel Farrell
@boyfarrell - 如果这是你需要的,你可以给我一份赏金)) - MikroDel
不错的尝试。这是可能的,我想找出如何做到。感谢您的帮助,但这并没有回答问题。 - Daniel Farrell
@boyfarrell - 这不是“尝试一下还不错”。如果可能的话,那么这就不是一个答案。我的意思是,如果不可能,那么这就是一个答案。- MikroDel - MikroDel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接