mouseExited не вызывается, когда мышь покидает trackArea во время прокрутки - PullRequest
29 голосов
/ 24 января 2012

Почему mouseExited / mouseEntered не вызывается, когда мышь выходит из NStrackingArea с помощью прокрутки или анимации?

Я создаю такой код:

Мышь вошла и вышла:

-(void)mouseEntered:(NSEvent *)theEvent {
    NSLog(@"Mouse entered");
}

-(void)mouseExited:(NSEvent *)theEvent
{
    NSLog(@"Mouse exited");
}

Область отслеживания:

-(void)updateTrackingAreas
{ 
    if(trackingArea != nil) {
        [self removeTrackingArea:trackingArea];
        [trackingArea release];
    }

    int opts = (NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways);
    trackingArea = [ [NSTrackingArea alloc] initWithRect:[self bounds]
                                             options:opts
                                               owner:self
                                            userInfo:nil];
    [self addTrackingArea:trackingArea];
}

Подробнее:

Я добавил NSViews как подпредставления в представлении NSScrollView.У каждого NSView есть своя собственная область отслеживания, и когда я прокручиваю свой scrollView и покидаю область отслеживания, «mouseExited» не вызывается, но без прокрутки все работает нормально.Проблема в том, что, когда я прокручиваю, вызывается updateTrackingAreas, и я думаю, что это создает проблемы.

* Та же проблема с просто NSView без добавления его в качестве подпредставления, так что это не проблема.

Ответы [ 2 ]

66 голосов
/ 02 февраля 2012

Как вы отметили в заголовке вопроса, mouseEntered и mouseExited вызываются только при движении мыши.Чтобы понять, почему это так, давайте сначала рассмотрим процесс добавления NSTrackingAreas в первый раз.

В качестве простого примера, давайте создадим представление, которое обычно рисует белый фон, но если пользователь наводит курсор мыши надвид, он рисует красный фон.В этом примере используется ARC.

@interface ExampleView

- (void) createTrackingArea

@property (nonatomic, retain) backgroundColor;
@property (nonatomic, retain) trackingArea;

@end

@implementation ExampleView

@synthesize backgroundColor;
@synthesize trackingArea

- (id) awakeFromNib
{
    [self setBackgroundColor: [NSColor whiteColor]];
    [self createTrackingArea];
}

- (void) createTrackingArea
{
    int opts = (NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways);
    trackingArea = [ [NSTrackingArea alloc] initWithRect:[self bounds]
                                             options:opts
                                               owner:self
                                            userInfo:nil];
    [self addTrackingArea:trackingArea];
}

- (void) drawRect: (NSRect) rect
{
    [[self backgroundColor] set];
    NSRectFill(rect);
}

- (void) mouseEntered: (NSEvent*) theEvent
{
    [self setBackgroundColor: [NSColor redColor]];
}

- (void) mouseEntered: (NSEvent*) theEvent
{
    [self setBackgroundColor: [NSColor whiteColor]];
}

@end

С этим кодом связаны две проблемы.Во-первых, когда вызывается -awakeFromNib, если мышь уже находится внутри представления, -mouseEntered не вызывается.Это означает, что фон по-прежнему будет белым, даже если мышь находится над видом.На самом деле это упоминается в документации NSView для параметра acceptInside -addTrackingRect: owner: userData: acceptInside:

Если YES, первое событие будет сгенерировано, когда курсор покинет aRect, независимо от того, находится ли курсорнаходится внутри aRect, когда добавлен отслеживающий прямоугольник.Если NO, первое событие будет сгенерировано, когда курсор покинет aRect, если курсор изначально находится внутри aRect, или когда курсор войдет в aRect, если курсор изначально находится вне aRect.

В обоих случаях, еслимышь находится в области отслеживания, никакие события не будут генерироваться, пока мышь не покинет область отслеживания.

Итак, чтобы исправить это, когда мы добавляем область отслеживания, нам нужно выяснить, находится ли курсор внутризона отслеживания.Наш метод -createTrackingArea, таким образом, становится

- (void) createTrackingArea
{
    int opts = (NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways);
    trackingArea = [ [NSTrackingArea alloc] initWithRect:[self bounds]
                                             options:opts
                                               owner:self
                                            userInfo:nil];
    [self addTrackingArea:trackingArea];

    NSPoint mouseLocation = [[self window] mouseLocationOutsideOfEventStream];
    mouseLocation = [self convertPoint: mouseLocation
                              fromView: nil];

    if (NSPointInRect(mouseLocation, [self bounds]))
    {
        [self mouseEntered: nil];
    }
    else
    {
        [self mouseExited: nil];
    }
}

Вторая проблема - прокрутка.При прокрутке или перемещении представления нам необходимо пересчитать NSTrackingAreas в этом представлении.Это делается путем удаления областей отслеживания и последующего добавления их обратно. Как вы заметили, -updateTrackingAreas вызывается при прокрутке представления.Это место для удаления и повторного добавления области.

- (void) updateTrackingAreas
{
    [self removeTrackingArea:trackingArea];
    [self createTrackingArea];
    [super updateTrackingAreas]; // Needed, according to the NSView documentation
}

И это должно решить вашу проблему.По общему признанию, необходимость находить местоположение мыши и затем преобразовывать ее для просмотра координат каждый раз, когда вы добавляете область отслеживания, быстро устаревает, поэтому я бы порекомендовал создать категорию в NSView, которая обрабатывает это автоматически.Вы не всегда сможете вызвать [self mouseEntered: nil] или [self mouseExited: nil], поэтому вы можете захотеть, чтобы категория принимала пару блоков.Один для запуска, если мышь находится в NSTrackingArea, а другой для запуска, если это не так.

4 голосов
/ 03 октября 2012

@ Майкл предлагает отличный ответ и решил мою проблему. Но есть одна вещь,

if (CGRectContainsPoint([self bounds], mouseLocation))
{
    [self mouseEntered: nil];
}
else
{
    [self mouseExited: nil];
}

Я нашел CGRectContainsPoint работы в моей коробке, а не CGPointInRect,

...