Pergunta

Por que mouseExited/mouseEntered não é chamado quando o mouse sai de NStrackingArea rolando ou fazendo animação?

Eu crio um código assim:

Mouse entrou e saiu:

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

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

Área de rastreamento:

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

Mais detalhes:

Adicionei NSViews como subvisualizações na visualização do NSScrollView.Cada NSView tem sua própria área de rastreamento e quando eu rolo meu scrollView e saio da área de rastreamento "mouseExited" não é chamado, mas sem rolar tudo funciona bem.O problema é que quando eu rolo "updateTrackingAreas" é chamado e acho que isso causa problemas.

* O mesmo problema apenas com o NSView sem adicioná-lo como subvisão, então isso não é um problema.

Foi útil?

Solução

Como você observou no título da pergunta, mouseEntered e mouseExited são chamados apenas quando o mouse se move.Para ver por que isso acontece, vamos primeiro examinar o processo de adição de NSTrackingAreas pela primeira vez.

Como um exemplo simples, vamos criar uma visualização que normalmente desenha um fundo branco, mas se o usuário passar o mouse sobre a visualização, ele desenha um fundo vermelho.Este exemplo usa 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

Existem dois problemas com este código.Primeiro, quando -awakeFromNib é chamado, se o mouse já estiver dentro da visualização, -mouseEntered não será chamado.Isso significa que o fundo ainda será branco, mesmo que o mouse esteja sobre a visualização.Na verdade, isso é mencionado na documentação do NSView para o parâmetro assumeInside de -addTrackingRect:owner:userData:assumeInside:

Se SIM, o primeiro evento será gerado quando o cursor sair de aRect, independentemente de o cursor estar dentro de aRect quando o retângulo de rastreamento for adicionado.Se NÃO, o primeiro evento será gerado quando o cursor sair de aRect se o cursor estiver inicialmente dentro de aRect, ou quando o cursor entrar em aRect se o cursor estiver inicialmente fora de aRect.

Em ambos os casos, se o mouse estiver dentro da área de rastreamento, nenhum evento será gerado até que o mouse saia da área de rastreamento.

Então, para corrigir isso, quando adicionamos a área de rastreamento, precisamos descobrir se o cursor está dentro da área de rastreamento.Nosso método -createTrackingArea torna-se assim

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

O segundo problema é a rolagem.Ao rolar ou mover uma visualização, precisamos recalcular o NSTrackingAreas nessa visualização.Isso é feito removendo as áreas de rastreamento e adicionando-as novamente.Como você observou, -updateTrackingAreas é chamado quando você rola a visualização.Este é o local para remover e adicionar novamente a área.

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

E isso deve resolver o seu problema.É certo que precisar encontrar a localização do mouse e depois convertê-lo para visualizar as coordenadas toda vez que você adiciona uma área de rastreamento é algo que envelhece rapidamente, então eu recomendaria criar uma categoria no NSView que lide com isso automaticamente.Você nem sempre poderá ligar para [self mouseEntered:nulo] ou [self mouseExited:nil], então você pode querer fazer com que a categoria aceite alguns blocos.Um para executar se o mouse estiver em NSTrackingArea e outro para executar se não estiver.

Outras dicas

@Michael oferece uma ótima resposta e resolveu meu problema.Mas há uma coisa,

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

eu encontrei CGRectContainsPoint funciona na minha caixa, não CGPointInRect,

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top