2012-01-23 4 views
27

Perché mouseExited/mouseEntered non viene chiamato quando il mouse esce da NStrackingArea scorrendo o eseguendo l'animazione?mouseExited non viene chiamato quando il mouse lascia trackingArea durante lo scorrimento

creo codice come questo:

mouse è entrato e uscito:

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

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

zona di inseguimento:

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

Maggiori dettagli:

Ho aggiunto NSViews come subviews in Vista di NSScrollView. Ogni NSView ha la propria area di tracciamento e quando faccio scorrere la mia scrollView e lascia l'area di tracciamento "mouseExited" non viene chiamato ma senza scorrere tutto funziona correttamente. Il problema è che quando faccio scorrere "updateTrackingAreas" viene chiamato e penso che questo faccia sorgere dei problemi.

* Lo stesso problema con solo NSView senza aggiungerlo come sottoview, quindi non è un problema.

+0

C'è alcune cose da prendere in considerazione. Qual è la superclasse? Sovrascrivi qualsiasi metodo di superclasse senza inviare super? Quindi, ecco le opzioni che passo sempre al trackingArea per essere sicuro che il mouse sia sempre tracciato: NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveInKeyWindow – Dimillian

+0

@ Dimillian77 Ho cambiato in "NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveInKeyWindow" ma non ha aiutato .. lo stesso problema . E senza "NSTrackingActiveAlways" non ha funzionato affatto .. Ho aggiornato la mia domanda che sia più chiara. –

+0

è necessario chiamare '[super updateTrackingAreas]'. E questo codice si trova all'interno delle sottoclassi NSViews o NSScrollView? –

risposta

63

Come indicato nel titolo della domanda, mouseEntered e mouseExited vengono chiamati solo quando il mouse si sposta. Per capire perché questo è il caso, diamo prima un'occhiata al processo di aggiunta di NSTrackingAreas per la prima volta.

Come semplice esempio, creiamo una vista che normalmente disegna uno sfondo bianco, ma se l'utente passa sopra la vista, disegna uno sfondo rosso. Questo esempio utilizza 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 

Ci sono due problemi con questo codice. Innanzitutto, quando viene chiamato -wakeFromNib, se il mouse è già all'interno della vista, -mouseEntered non viene chiamato. Ciò significa che lo sfondo sarà ancora bianco, anche se il mouse è sopra la vista. Questo in realtà è menzionato nella documentazione NSView per il parametro assumeInside di -addTrackingRect: proprietario: userData: assumeInside:

Se SI, il primo evento verrà generato quando il cursore lascia aRect, a prescindere se il cursore si trova all'interno aRect quando viene aggiunto il rettangolo di tracciamento. Se NO il primo evento verrà generato quando il cursore lascia aRect se il cursore è inizialmente all'interno diRect, o quando il cursore entra inRect se il cursore è inizialmente all'esterno diRect.

In entrambi i casi, se il mouse si trova all'interno dell'area di tracciamento, non verranno generati eventi fino a quando il mouse non lascia l'area di tracciamento.

Quindi per risolvere questo problema, quando aggiungiamo l'area di rilevamento, dobbiamo scoprire se il cursore si trova all'interno dell'area di tracciamento. Il nostro metodo -createTrackingArea diventa quindi

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

Il secondo problema è lo scorrimento. Quando si scorre o si sposta una vista, è necessario ricalcolare NSTrackingAreas in quella vista. Questo viene fatto rimuovendo le aree di tracciamento e quindi aggiungendole di nuovo. Come hai notato, -updateTrackingAreas viene chiamato quando si scorre la vista. Questo è il posto dove rimuovere e aggiungere nuovamente l'area.

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

E che dovrebbe prendersi cura del vostro problema.Certo, il bisogno di trovare la posizione del mouse e quindi convertirlo per visualizzare le coordinate ogni volta che aggiungi un'area di tracciamento è qualcosa che invecchia rapidamente, quindi ti consiglio di creare una categoria su NSView che la gestisca automaticamente. Non sarai sempre in grado di chiamare [self mouseEntered: nil] o [self mouseExited: nil], quindi potresti voler fare in modo che la categoria accetti un paio di blocchi. Uno da eseguire se il mouse si trova in NSTrackingArea e uno da eseguire se non lo è.

+3

Grazie per la tua chiara risposta molto buona! Ho appena aggiornato alcune righe del tuo codice e funziona perfettamente! Quindi ora posso assegnarti la mia taglia e +1, grazie! –

+0

Grazie mille! Ho salvato la mia giornata. Il più grande errore che ho fatto è stato, supponendo che NSTrackingInVisibleRect si prenderà cura di esso come si dice su http://stackoverflow.com/a/4137243/804616 che non sembra essere abbastanza buono per NSOutlineView (non ho provato un isolato esempio per verificare se funziona come previsto altrimenti). – trss

+0

Grazie anche a me, ha funzionato alla grande! –

3

@ Michael offre un'ottima risposta e ha risolto il mio problema. Ma c'è una cosa,

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

ho trovato CGRectContainsPoint opere nella mia scatola, non CGPointInRect,

+1

Hai ragione che CGPointInRect non è una funzione standard. Intendevo utilizzare "NSPointInRect", che fa parte di Foundation. Raccomanderei di utilizzare NSPointInRect quando si ha a che fare con NSPoints e CGRectContainsPoint quando si lavora con CGPoint, dato che NSRect e CGPoint sono in realtà diverse strutture. In ogni caso, grazie per aver segnalato l'errore. Ho aggiornato la mia risposta originale per correggerla. –

+1

@MichaelBuckley qual è la differenza tra NSPoint e CGPoint, presumo che siano uguali, uno solo per il cacao e uno per il carbonio? – fengd

+1

Guardando di nuovo, le due strutture sono identiche. Grazie per avermi corretto di nuovo. Sono entrambi stati in OS X dalla prima versione e l'unica differenza è che NSPoint è definito in Foundation e CGPoint è definito in ApplicationServices. Né sono strettamente Carbonio, sebbene Carbon utilizzi CGPoint. In realtà stavo pensando a NSRange e CFRange, che sono potenzialmente diversi. NSRange utilizza membri di NSUinteger e CFRange utilizza membri di CFIndex. Queste due strutture possono essere uguali a seconda dell'architettura, ma non è garantito che siano uguali. –

Problemi correlati