2012-01-23 35 views
27

爲什麼當鼠標通過滾動或進行動畫從NStrackingArea退出時不會調用mouseExited/mouseEntered?當鼠標在滾動時離開trackingArea時不會調用mouseExited

我創造這樣的代碼:

鼠標進入和退出:

-(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相同的問題,而不添加它作爲子視圖,所以這不是一個問題。

+0

有幾件事情要考慮。什麼是超類?你是否覆蓋任何超類方法而不發送超類?然後,這裏是我總是傳遞給trackingArea的選項,以確保鼠標實際上始終被跟蹤:NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveInKeyWindow – Dimillian

+0

@ Dimillian77我改爲了「NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveInKeyWindow」,但沒有幫助..同樣的問題。沒有「NSTrackingActiveAlways」,它根本就不起作用。我更新了我的問題,使它更清晰。 –

+0

你需要調用'[super updateTrackingAreas]'。而這段代碼是在NSViews的子類或NSScrollView中? –

回答

63

正如您在問題的標題中指出的那樣,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文檔-addTrackingRect的assumeInside參數中實際提到:所有者:用戶數據:assumeInside:

如果是,將被當光標離開aRect,無論如果光標位於內部aRect生成的第一事件當追蹤矩形被添加時。如果否,當光標離開aRect時,如果光標最初位於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中,則運行一個,如果不是,則運行一個。

+3

非常感謝您的回覆!我只是更新了你的代碼的一些行,它完美的工作!所以現在我可以授予您我的賞金和+1,謝謝! –

+0

非常感謝!保存了我的一天。我所做的最大的錯誤是,假設NSTrackingInVisibleRect將像它在http://stackoverflow.com/a/4137243/804616中所說的那樣處理它,這對NSOutlineView來說似乎不夠好(還沒有嘗試過隔離例如檢查它是否按預期的那樣發揮作用)。 – trss

+0

也謝謝你,這個工作很棒! –

3

@Michael提供了一個很好的答案,並解決了我的問題。但有一兩件事,

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

我發現我的盒子CGRectContainsPoint作品,而不是CGPointInRect

+1

你是正確的,CGPointInRect不是一個標準功能。我打算使用「NSPointInRect」,這是基金會的一部分。我建議在處理NSPoint時使用NSPointInRect,使用CGPoint時使用CGRectContainsPoint,因爲NSRect和CGPoint實際上是不同的結構。無論如何,謝謝你指出錯誤。我已更新我的原始答案以糾正它。 –

+1

@MichaelBuckley NSPoint和CGPoint有什麼區別,我認爲它們是一樣的,只有一個用於可可和一個用於碳? – fengd

+1

再看一遍,這兩個結構是相同的。感謝您再次糾正我。自從第一版以來,他們都在OS X中,唯一的區別是NSPoint在Foundation中定義,CGPoint在ApplicationServices中定義。雖然Carbon使用CGPoint,但它們都不是嚴格的Carbon。我實際上正在考慮可能不同的NSRange和CFRange。 NSRange使用NSUinteger成員,CFRange使用CFIndex成員。這兩個結構可能是相同的,這取決於體系結構,但不能保證是相同的。 –

相關問題