Проблема с отслеживанием события мыши при прокрутке в NSTableCellView

У меня есть всплывающие кнопки в пользовательском представлении ячейки таблицы, и когда курсор мыши перемещается над одной ячейкой, эти кнопки ячейки будут отображаться, и только эта ячейка должна отображать кнопки. Если я перемещаю курсор мыши медленно, все работает правильно, но когда я прокручиваю представление таблицы с помощью средней скорости мыши, появляется слишком много ячеек с всплывающими кнопками, чего действительно следует избегать. Почему-то событие мыши не отслеживается правильно при прокрутке. Я получил этот код отслеживания из библиотеки примеров Apple. Не могли бы вы дать какое-нибудь предложение по этому вопросу?

#import "BasisCellView.h"

@implementation BasisCellView

- (void)drawRect:(NSRect)dirtyRect {
    [super drawRect:dirtyRect];
    // Drawing code here.
    [[NSImage imageNamed:@"background"] drawInRect:dirtyRect fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:0.1];
}

- (void)setBackgroundStyle:(NSBackgroundStyle)backgroundStyle {
    [super setBackgroundStyle: NSBackgroundStyleLight];
}

- (void)setMouseInside:(BOOL)value {
    if (mouseInside != value) {
        mouseInside = value;
        [self.deleteButton setHidden:!value];
        [self.bookmarkButton setHidden:!value];
        [self setNeedsDisplay:YES];
        NSLog(@"redrawn");
    }
}

- (BOOL)mouseInside {
    return mouseInside;
}

- (void)ensureTrackingArea {
    if (trackingArea == nil) {
        trackingArea = [[NSTrackingArea alloc] initWithRect:NSZeroRect options:NSTrackingInVisibleRect | NSTrackingActiveAlways | NSTrackingMouseEnteredAndExited owner:self userInfo:nil];
    }
}

- (void)updateTrackingAreas {
    [super updateTrackingAreas];
    [self ensureTrackingArea];
    if (![[self trackingAreas] containsObject:trackingArea]) {
        [self addTrackingArea:trackingArea];
    }
}

- (void)mouseEntered:(NSEvent *)theEvent {
    NSLog(@"1");
    self.mouseInside = YES;
}

- (void)mouseExited:(NSEvent *)theEvent {
    NSLog(@"0");
    self.mouseInside = NO;
}

@end

А вот и распечатанный лог:

2015-02-05 08:59:33.267 Clever[1286:25969] 1
2015-02-05 08:59:33.267 Clever[1286:25969] redrawn
2015-02-05 08:59:33.299 Clever[1286:25969] 0
2015-02-05 08:59:33.299 Clever[1286:25969] redrawn
2015-02-05 08:59:33.333 Clever[1286:25969] 1
2015-02-05 08:59:33.333 Clever[1286:25969] redrawn
2015-02-05 08:59:33.350 Clever[1286:25969] 0
2015-02-05 08:59:33.350 Clever[1286:25969] redrawn
2015-02-05 08:59:33.382 Clever[1286:25969] 1
2015-02-05 08:59:33.383 Clever[1286:25969] redrawn
2015-02-05 08:59:33.669 Clever[1286:25969] 1
2015-02-05 08:59:33.669 Clever[1286:25969] redrawn
2015-02-05 08:59:33.736 Clever[1286:25969] 1
2015-02-05 08:59:33.736 Clever[1286:25969] redrawn
2015-02-05 08:59:33.769 Clever[1286:25969] 0
2015-02-05 08:59:33.769 Clever[1286:25969] redrawn
2015-02-05 08:59:33.769 Clever[1286:25969] 1
2015-02-05 08:59:33.770 Clever[1286:25969] redrawn
2015-02-05 08:59:34.101 Clever[1286:25969] 1
2015-02-05 08:59:34.101 Clever[1286:25969] redrawn
2015-02-05 08:59:34.102 Clever[1286:25969] 0
2015-02-05 08:59:34.102 Clever[1286:25969] redrawn
2015-02-05 08:59:34.136 Clever[1286:25969] 0
2015-02-05 08:59:34.136 Clever[1286:25969] redrawn
2015-02-05 08:59:34.150 Clever[1286:25969] 1
2015-02-05 08:59:34.150 Clever[1286:25969] redrawn
2015-02-05 08:59:34.187 Clever[1286:25969] 1
2015-02-05 08:59:34.187 Clever[1286:25969] redrawn
2015-02-05 08:59:34.235 Clever[1286:25969] 1
2015-02-05 08:59:34.272 Clever[1286:25969] 0

person Quang Nguyen    schedule 05.02.2015    source источник
comment
Я могу предложить вам один обходной путь для этой проблемы. Когда мышь входит в один вид ячейки, вы можете сделать другие виды мышью внутри НЕТ.   -  person Sheen Vempeny    schedule 05.02.2015
comment
Спасибо за ваше предложение, теперь оно исправлено с помощью вашего решения, но единственное ли это решение? Я думаю о производительности, потому что мне нужны вложенные циклы, чтобы получить представление ячейки из табличного представления ( TableView -> RowView -> CellView ).   -  person Quang Nguyen    schedule 06.02.2015
comment
В противном случае вы можете отслеживать прямоугольник представления таблицы, а с помощью события mouseMoved вы можете присвоить значение mouseInside для представления ячейки.   -  person Sheen Vempeny    schedule 06.02.2015


Ответы (3)


Вот краткая версия кода, необходимого для настройки области отслеживания:

class MyCustomTableCellView: NSTableCellView {

    func setUpTrackingArea()
    {
        let trackingArea = NSTrackingArea(rect: self.frame, options: [NSTrackingAreaOptions.MouseEnteredAndExited, NSTrackingAreaOptions.ActiveAlways], owner: self, userInfo: nil)
        self.addTrackingArea(trackingArea)
    }

    override init(frame frameRect: NSRect) {
        super.init(frame: frameRect)

        setUpTrackingArea()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)

        setUpTrackingArea()
    }

    override func mouseEntered(theEvent: NSEvent) {
        Swift.print("mouse Entered")
    }

    override func mouseExited(theEvent: NSEvent) {
        Swift.print("mouse exited")
    }
}
person Michael Dautermann    schedule 09.09.2016
comment
Это не работает, если представление прокручивается - вы никогда не получаете событие выхода из мыши. - person Duncan Groenewald; 22.05.2020

Reminders.app использует NSTrackingArea для контроллера cellView +, наблюдая за NSScrollViewWillStartLiveScrollNotification и перебирая видимые ячейки, чтобы скрыть кнопку.

Если вам не нужны оперативные обновления, и вы можете скрыть представления/отключить выделение, немедленно используйте NSScrollViewWillStartLiveScrollNotification

Для оперативных обновлений:

- (void)touchesMovedWithEvent:(NSEvent *)event;
[self setAcceptsTouchEvents:YES];

Все остальное настраивается с несколькими решениями: например. используйте NSScrollViewWillStartLiveScrollNotification + NSScrollViewDidEndLiveScrollNotification в вашем контроллере или вы переопределяете метод scrollWheel и запускаете события мыши так, как вам нужно:

CustomScrollView — это тот, кто отправляет mouseEvents в CustomTableRowView, а CustomTableRowView перенаправляет его своим подпредставлениям.

#import <Cocoa/Cocoa.h>

@interface CustomScrollView : NSScrollView

@end

#import "CustomScrollView.h"

@implementation CustomScrollView

- (void)scrollWheel:(NSEvent *)theEvent
{
    NSPoint mouseLocation;
    NSInteger rowBefore = -1, rowAfter = -1;
    mouseLocation = [[self documentView] convertPoint:[theEvent locationInWindow] fromView:nil];
    rowBefore = [(NSTableView *)[self documentView] rowAtPoint:mouseLocation];

    @autoreleasepool {
        while ((theEvent = [[self window] nextEventMatchingMask:(NSScrollWheelMask)
                                                      untilDate:[NSDate distantFuture]
                                                         inMode:NSEventTrackingRunLoopMode
                                                        dequeue:YES]) &&
               !(([theEvent phase] & NSEventPhaseCancelled) || ([theEvent phase] & NSEventPhaseEnded))) {
            [super scrollWheel:theEvent];
        }
    }
    [super scrollWheel:theEvent];

    mouseLocation = [[self documentView] convertPoint:[theEvent locationInWindow] fromView:nil];
    rowAfter = [(NSTableView *)[self documentView] rowAtPoint:mouseLocation];
        if (rowBefore != -1) {
            NSTableRowView *rowViewBefore = [(NSTableView *)[self documentView] rowViewAtRow:rowBefore makeIfNecessary:NO];
            [rowViewBefore mouseExited:[NSApp currentEvent]];
        }
        if (rowAfter != -1) {
            NSTableRowView *rowViewAfter = [(NSTableView *)[self documentView] rowViewAtRow:rowAfter makeIfNecessary:NO];
            [rowViewAfter mouseEntered:[NSApp currentEvent]];
        }
}

@end

CustomTableRowView:

- (void)mouseEntered:(NSEvent *)event
{
    if (_inMouseEntered == NO) {
        _inMouseEntered = YES;
        [self setHighlighted:YES];
        for (NSView *view in [self subviews]) {
            if ([view isKindOfClass:[NSTableCellView class]]) {
                [view mouseEntered:event];
            }
        }
        [self setNeedsDisplay:YES];
        _inMouseEntered = NO;
    }
}

- (void)mouseExited:(NSEvent*)event
{
    if (_inMouseExited == NO) {
        _inMouseExited = YES;
        [self setHighlighted:NO];
        for (NSView *view in [self subviews]) {
            if ([view isKindOfClass:[NSTableCellView class]]) {
                [(NSTableCellView *)view mouseExited:event];
            }
        }
        [self setNeedsDisplay:YES];
        _inMouseExited = NO;
    }
}

Не забудьте NSTrackingArea, чтобы получить исходные события mouseEvents.

person Marek H    schedule 30.12.2017
comment
Существует недокументированный NSTrackingRollover (NSTrackingArea) со значением 0x800, но мне не удалось с его помощью вызвать событие mouseExit. - person Marek H; 30.12.2017

Этот код делает именно то, что вы хотите, как в приложении Apple Reminders. Если внимательно посмотреть в приложении «Напоминания», они помещают некоторую задержку, прежде чем сделать кнопку видимой. Я добавляю много строк в таблицу и тестирую при прокрутке.

  #import "OTratingListTableCellView.h"

    @implementation OTratingListTableCellView

    @synthesize boatNameTextField,boatRatingTextField,boatStartTimeTextField,boatFinishTimeTextField,classTextField,popUpButton;

    - (void)drawRect:(NSRect)dirtyRect {
        [super drawRect:dirtyRect];


        NSTrackingArea *trackingArea = [[NSTrackingArea alloc] initWithRect:self.frame
                                                                    options: (NSTrackingMouseEnteredAndExited | NSTrackingActiveInKeyWindow )
                                                                      owner:self userInfo:nil];
        [self addTrackingArea:trackingArea];
        // Drawing code here.
    }

    - (void)mouseEntered:(NSEvent *)theEvent
    {
        NSLog(@"mouseEntered");
        popUpButton.hidden=false;

    }
    - (void)mouseExited:(NSEvent *)theEvent
    {
        popUpButton.hidden=true;

        NSLog(@"mouseExited");  
    }

    @end
person codezero    schedule 20.04.2016
comment
Добро пожаловать в СО. Пожалуйста, объясните свой ответ, а не просто разместите код. - person bastelflp; 21.04.2016
comment
В вашем примере кода есть ошибка производительности, когда вы снова и снова добавляете области отслеживания с помощью функции drawRect: (которая вызывается очень часто). Лучше просто создать trackingArea один раз (например, в init) - person Michael Dautermann; 09.09.2016