Реагировать на события мыши в текстовом поле в табличном представлении на основе представления - PullRequest
27 голосов
/ 18 августа 2011

У меня есть текстовые поля внутри пользовательского представления внутри NSOutlineView. Для редактирования одной из этих ячеек требуется один клик, пауза и еще один клик. Первый единственный щелчок выбирает строку представления таблицы, а второй единственный щелчок рисует курсор в поле. Двойной щелчок по ячейке, позволяющий редактировать в табличном представлении на основе ячеек, выбирает только строку.

Поведение, которое я хочу: одним щелчком мыши изменить выбор и отредактировать.

Что мне нужно переопределить, чтобы получить это поведение?

Я прочитал несколько других сообщений:

  • Шаблон NSTextField flyweight , по-видимому, не применим к табличным представлениям на основе представлений, где все представления ячеек создаются из перьев.
  • Я попытался создать подкласс NSTextField, как , это решение описывает , но мой переопределенный метод mouseDown не вызывается. Переопределены awakeFromNib и viewWillDraw (упомянутые в этом посте ) вызваны. Конечно, mouseDown вызывается, если я помещаю текстовое поле где-нибудь за пределами табличного представления.

Для сравнения, NSSegmentedControl в моем представлении ячейки меняет свое значение без предварительного выбора строки.


Вот рабочее решение , адаптированное из принятого ответа:

В подклассе контурного вида:

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

Ответы [ 5 ]

15 голосов
/ 19 августа 2011

Я постараюсь вернуть услугу ... Подкласс 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];
    }
}
14 голосов
/ 26 марта 2013

Была такая же проблема.После долгой борьбы он волшебным образом работал, когда я выбрал None вместо значения по умолчанию Regular (другой вариант - Source List) для опции Highlight представления таблицы в IB!

Другой вариант -решение на https://stackoverflow.com/a/13579469/804616,, которое кажется более конкретным, но немного хакерским по сравнению с этим.

4 голосов
/ 08 мая 2017

Вы действительно хотите переопределить 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.
        ...

     }
3 голосов
/ 01 января 2015

Я написал следующее, чтобы поддержать случай, когда у вас есть более сложный 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];
                });
        }
    }
}
1 голос
/ 17 ноября 2012

Просто хочу отметить, что если все, что вам нужно, это только редактирование (то есть в таблице без выделения), переопределение -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];
}
...