Подтвердить что ты не робот

MouseExited не вызывается, когда мышь отслеживает отслеживание во время прокрутки

Почему 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, не добавляя ее в качестве subview, чтобы не проблема.

4b9b3361

Ответ 1

Как вы отметили в заголовке вопроса, 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: Предпочесть:

Если 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, а другая для запуска, если это не так.

Ответ 2

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

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

Я обнаружил, что CGRectContainsPoint работает в моей коробке, а не CGPointInRect,