29

我在NSOutlineView. 编辑其中一个单元格需要单击、暂停和再次单击。第一次单击选择表格视图行,第二次单击在字段中绘制光标。双击单元格,您可以在基于单元格的表格视图中进行编辑,只选择行。

我想要的行为:一键更改选择和编辑。

我需要覆盖什么才能获得这种行为?

我读过其他一些帖子:

  • NSTextField 享元模式似乎不适用于基于视图的表格视图,其中单元格视图都是从 nib 实例化的。
  • 我尝试NSTextField这个解决方案描述的那样进行子类化,但是没有调用我的覆盖mouseDown方法。覆盖awakeFromNib和(在这篇文章viewWillDraw中提到)调用。如果我将文本字段放在表格视图之外的某个地方,当然会调用它。mouseDown

相比之下,NSSegmentedControl我的单元格视图中的 a 在没有先选择行的情况下更改了它的值。


这是根据接受的响应改编的工作解决方案:

在大纲视图子类中:

-(void)mouseDown:(NSEvent *)theEvent {
    [super mouseDown:theEvent];

    // Forward the click to the row's cell view
    NSPoint selfPoint = [self convertPoint:theEvent.locationInWindow fromView:nil];
    NSInteger row = [self rowAtPoint:selfPoint];
    if (row>=0) [(CellViewSubclass *)[self viewAtColumn:0 row:row makeIfNecessary:NO]
            mouseDownForTextFields:theEvent];
}

在表格单元格视图子类中:

// Respond to clicks within text fields only, because other clicks will be duplicates of events passed to mouseDown
- (void)mouseDownForTextFields:(NSEvent *)theEvent {
    // If shift or command are being held, we're selecting rows, so ignore
    if ((NSCommandKeyMask | NSShiftKeyMask) & [theEvent modifierFlags]) return;
    NSPoint selfPoint = [self convertPoint:theEvent.locationInWindow fromView:nil];
    for (NSView *subview in [self subviews])
        if ([subview isKindOfClass:[NSTextField class]])
            if (NSPointInRect(selfPoint, [subview frame]))
                [[self window] makeFirstResponder:subview];
}
4

6 回答 6

16

有同样的问题。经过一番努力,当我在 IB 中选择None默认Regular(其他选项是Source List)作为表格视图选项时,它神奇地起作用了!Highlight

另一种选择是https://stackoverflow.com/a/13579469/804616上的解决方案,与此相比,它似乎更具体但有点 hacky。

于 2013-03-26T09:59:53.950 回答
15

我会尽力报答...子类并像这样NSOutlineView覆盖:-mouseDown:

- (void)mouseDown:(NSEvent *)theEvent {
    [super mouseDown:theEvent];

    // Only take effect for double clicks; remove to allow for single clicks
    if (theEvent.clickCount < 2) {
        return;
    }

    // Get the row on which the user clicked
    NSPoint localPoint = [self convertPoint:theEvent.locationInWindow
                                   fromView:nil];
    NSInteger row = [self rowAtPoint:localPoint];

    // If the user didn't click on a row, we're done
    if (row < 0) {
        return;
    }

    // Get the view clicked on
    NSTableCellView *view = [self viewAtColumn:0 row:row makeIfNecessary:NO];

    // If the field can be edited, pop the editor into edit mode
    if (view.textField.isEditable) {
        [[view window] makeFirstResponder:view.textField];
    }
}
于 2011-08-19T18:58:26.780 回答
5

您确实想覆盖 validateProposedFirstResponder 并根据您的逻辑允许(或不)特定的第一响应者。NSTableView 中的实现(有点)像这样(我将它重写为伪代码):

- (BOOL)validateProposedFirstResponder:(NSResponder *)responder forEvent:(NSEvent *)event {

// We want to not do anything for the following conditions:
// 1. We aren't view based (sometimes people have subviews in tables when they aren't view based)
// 2. The responder to valididate is ourselves (we send this up the chain, in case we are in another tableview)
// 3. We don't have a selection highlight style; in that case, we just let things go through, since the user can't appear to select anything anyways.
if (!isViewBased || responder == self || [self selectionHighlightStyle] == NSTableViewSelectionHighlightStyleNone) {
    return [super validateProposedFirstResponder:responder forEvent:event];
}

if (![responder isKindOfClass:[NSControl class]]) {
    // Let any non-control become first responder whenever it wants
    result = YES;
    // Exclude NSTableCellView. 
    if ([responder isKindOfClass:[NSTableCellView class]]) {
        result = NO;
    }

} else if ([responder isKindOfClass:[NSButton class]]) {
    // Let all buttons go through; this would be caught later on in our hit testing, but we also do it here to make it cleaner and easier to read what we want. We want buttons to track at anytime without any restrictions. They are always valid to become the first responder. Text editing isn't.
    result = YES;
} else if (event == nil) {
    // If we don't have any event, then we will consider it valid only if it is already the first responder
    NSResponder *currentResponder = self.window.firstResponder;
    if (currentResponder != nil && [currentResponder isKindOfClass:[NSView class]] && [(NSView *)currentResponder isDescendantOf:(NSView *)responder]) {
        result = YES;
    }
} else {
    if ([event type] == NSEventTypeLeftMouseDown || [event type] == NSEventTypeRightMouseDown) {
        // If it was a double click, and we have a double action, then send that to the table
        if ([self doubleAction] != NULL && [event clickCount] > 1) {
           [cancel the first responder delay];
        }
   ...
          The code here checks to see if the text field 
        cell had text hit. If it did, it attempts to edit it on a delay. 
        Editing is simply making that NSTextField the first responder.
        ...

     }
于 2017-05-08T16:18:34.400 回答
3

我写了以下内容来支持当您有一个具有多个文本字段的更复杂的 NSTableViewCell 或文本字段不占据整个单元格时的情况。这里有一个翻转 y 值的技巧,因为当您在 NSOutlineView 或 NSTableView 和它的 NSTableCellViews 之间切换时,坐标系会被翻转。

- (void)mouseDown:(NSEvent *)theEvent
{
    [super mouseDown: theEvent];

    NSPoint thePoint = [self.window.contentView convertPoint: theEvent.locationInWindow
                                                      toView: self];
    NSInteger row = [self rowAtPoint: thePoint];
    if (row != -1) {
        NSView *view = [self viewAtColumn: 0
                                      row: row
                          makeIfNecessary: NO];

        thePoint = [view convertPoint: thePoint
                             fromView: self];
        if ([view isFlipped] != [self isFlipped])
            thePoint.y = RectGetHeight(view.bounds) - thePoint.y;

        view = [view hitTest: thePoint];
        if ([view isKindOfClass: [NSTextField class]]) {
            NSTextField *textField = (NSTextField *)view;
            if (textField.isEnabled && textField.window.firstResponder != textField)

                dispatch_async(dispatch_get_main_queue(), ^{
                    [textField selectText: nil];
                });
        }
    }
}
于 2015-01-01T01:54:35.540 回答
2

只是想指出,如果你想要的只是编辑(即在没有选择的表中),覆盖-hitTest:似乎更简单,更像可可:

- (NSView *)hitTest:(NSPoint)aPoint
{
    NSInteger column = [self columnAtPoint: aPoint];
    NSInteger row = [self rowAtPoint: aPoint];

    // Give cell view a chance to override table hit testing
    if (row != -1 && column != -1) {
        NSView *cell = [self viewAtColumn:column row:row makeIfNecessary:NO];

        // Use cell frame, since convertPoint: doesn't always seem to work.
        NSRect frame = [self frameOfCellAtColumn:column row:row];
        NSView *hit = [cell hitTest: NSMakePoint(aPoint.x + frame.origin.x, aPoint.y + frame.origin.y)];

        if (hit)
            return hit;
    }

    // Default implementation
    return [super hitTest: aPoint];
}
于 2012-11-17T13:26:30.100 回答
1

这是@Dov 答案的swift 4.2版本:

 override func mouseDown(with event: NSEvent) {
    super.mouseDown(with: event)

    if (event.clickCount < 2) {
        return;
    }
    // Get the row on which the user clicked
    let localPoint = self.convert(event.locationInWindow, from: nil)


    let row = self.row(at: localPoint)

    // If the user didn't click on a row, we're done
    if (row < 0) {
        return
    }

    DispatchQueue.main.async {[weak self] in
        guard let self = self else {return}
        // Get the view clicked on
        if let clickedCell = self.view(atColumn: 0, row: row, makeIfNecessary: false) as? YourOutlineViewCellClass{

            let pointInCell = clickedCell.convert(localPoint, from: self)

            if (clickedCell.txtField.isEditable && clickedCell.txtField.hitTest(pointInCell) != nil){

                clickedCell.window?.makeFirstResponder(clickedCell.txtField)

            }

        }
    }

}
于 2020-03-24T19:10:49.377 回答