2012-01-23 17 views
27

¿Por qué no se invoca mouseExited/mouseEntered cuando el mouse sale de NStrackingArea desplazándose o haciendo animación?mouseExited no se llama cuando el mouse abandona trackingArea mientras se desplaza

puedo crear un código como éste:

ratón entraba y salía:

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

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

área de seguimiento:

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

Más detalles:

He añadido NSViews como subvistas en Vista de NSScrollView. Cada NSView tiene su propia área de seguimiento y cuando me desplazo por scrollView y salgo del área de seguimiento, no se llama "mouseExited", pero sin desplazarme todo funciona bien. El problema es que cuando me desplazo "updateTrackingAreas" se llama y creo que esto crea problemas.

* Mismo problema con solo NSView sin agregarlo como subvista, así que no hay problema.

+0

Hay algunas cosas a tener en cuenta. ¿Cuál es la superclase? ¿Sobreescribes cualquier método de superclase sin enviar super? Entonces, aquí hay opciones que siempre paso al trackingArea para asegurarme de que el mouse siempre se rastrea: NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveInKeyWindow – Dimillian

+0

@ Dimillian77 Cambié a "NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveInKeyWindow" pero eso no ayudó ... mismo problema . Y sin "NSTrackingActiveAlways" no funcionó en absoluto ... Actualicé mi pregunta para que sea más clara. –

+0

necesita llamar '[super updateTrackingAreas]'. ¿Y este código está dentro de las subclases NSViews o NSScrollView? –

Respuesta

63

Como ha indicado en el título de la pregunta, mouseEntered y mouseExited solo se invocan cuando se mueve el mouse. Para ver por qué este es el caso, veamos primero el proceso de agregar NSTrackingAreas por primera vez.

Como un ejemplo simple, creemos una vista que normalmente dibuja un fondo blanco, pero si el usuario se desplaza sobre la vista, dibuja un fondo rojo. Este ejemplo 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 

Existen dos problemas con este código. Primero, cuando se llama a -awakeFromNib, si el mouse ya está dentro de la vista, -mouseEntered no se llama. Esto significa que el fondo seguirá siendo blanco, aunque el mouse esté sobre la vista. Esto es en realidad mencionado en la documentación para el parámetro NSView assumeInside de -addTrackingRect: propietario: userData: assumeInside:

En caso afirmativo, se generará el primer evento cuando el cursor deja aRect, sin tener en cuenta si el cursor está dentro de aRect cuando se agrega el rectángulo de seguimiento. Si NO, el primer evento se generará cuando el cursor salga a Rect si el cursor está inicialmente dentro de aRect, o cuando el cursor ingrese aRect si el cursor está inicialmente fuera de aRect.

En ambos casos, si el mouse está dentro del área de seguimiento, no se generarán eventos hasta que el mouse abandone el área de seguimiento.

Para solucionar esto, cuando agreguemos el área de seguimiento, debemos averiguar si el cursor está dentro del área de seguimiento. Nuestro método -createTrackingArea se convierte así en

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

El segundo problema es el desplazamiento. Al desplazar o mover una vista, debemos volver a calcular las NSTrackingAreas en esa vista. Esto se hace eliminando las áreas de seguimiento y luego volviéndolas a agregar. Como anotó, se llama a -updateTrackingAreas cuando se desplaza la vista. Este es el lugar para eliminar y volver a agregar el área.

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

Y eso debería encargarse de su problema.Es cierto que la necesidad de encontrar la ubicación del mouse y luego convertirla para ver las coordenadas cada vez que agrega un área de seguimiento es algo que envejece rápidamente, por lo que recomendaría crear una categoría en NSView que maneje esto automáticamente. No siempre podrá llamar a [self mouseEntered: nil] o [self mouseExited: nil], por lo que es posible que desee que la categoría acepte un par de bloques. Uno para ejecutar si el mouse está en NSTrackingArea, y uno para ejecutar si no lo está.

+3

Gracias por su clara muy buena respuesta! ¡Acabo de actualizar algunas líneas de tu código y funciona perfectamente! Así que ahora puedo recompensarte con mi recompensa y +1, ¡gracias! –

+0

¡Muchas gracias! Me salvó el día. El error más grande que cometí fue que asumí que NSTrackingInVisibleRect se encargaría de ello como dice en http://stackoverflow.com/a/4137243/804616, que no parece ser lo suficientemente bueno para NSOutlineView (no he probado un aislado ejemplo para verificar si actúa como se espera de otra manera). – trss

+0

¡Gracias a mí también, esto funcionó muy bien! –

3

@Michael ofrece una gran respuesta y solucionó mi problema. Pero hay una cosa,

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

He encontrado CGRectContainsPoint obras en mi caja, no CGPointInRect,

+1

Tiene razón en que CGPointInRect no es una función estándar. Quise utilizar "NSPointInRect", que es parte de Foundation. Recomendaría utilizar NSPointInRect cuando se trata de NSPoints, y CGRectContainsPoint cuando se trabaja con CGPoint, ya que NSRect y CGPoint son en realidad estructuras diferentes. En cualquier caso, gracias por señalar el error. He actualizado mi respuesta original para corregirlo. –

+1

@MichaelBuckley ¿cuál es la diferencia entre NSPoint y CGPoint, supongo que son los mismos, solo uno para el cacao y uno para el carbono? – fengd

+1

Viéndolo de nuevo, las dos estructuras son idénticas. Gracias por corregirme de nuevo. Ambos han estado en OS X desde la primera versión, y la única diferencia es que NSPoint se define en Foundation y CGPoint se define en ApplicationServices. Ninguno de los dos es estrictamente carbono, aunque Carbon usa CGPoint. Estaba pensando en NSRange y CFRange, que son potencialmente diferentes. NSRange usa miembros NSUinteger, y CFRange usa miembros CFIndex. Estas dos estructuras pueden ser las mismas dependiendo de la arquitectura, pero no se garantiza que sean iguales. –

Cuestiones relacionadas