mouseExited não é chamado quando o mouse sai de trackArea durante a rolagem
-
12-11-2019 - |
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.
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
,