鼠标离开跟踪时不调用鼠标退出区域



当鼠标通过滚动或动画从NStrackingArea退出时,为什么不调用mouseExited/mouseEntered?

我创建这样的代码:

鼠标进入和退出:

-(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添加为NSCrollView视图中的子视图。每个NSView都有自己的跟踪区域,当我滚动我的scrollView并离开跟踪区域时,不会调用"mouseExited",但如果不滚动,一切都很好。问题是,当我滚动时,会调用"updateTrackingAreas",我认为这会产生问题。

*NSView也有同样的问题,但没有将其添加为子视图,所以这不是问题。

如您在问题标题中所述,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时,如果鼠标已经在视图中,则不会调用-moseEntered。这意味着即使鼠标在视图上,背景仍然是白色的。这实际上在NSView文档中提到了-addTrackingRect:owner:userData:assumeInside:的assumeInside参数

如果是,则当光标离开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-MouseExitd:nil],因此您可能希望使类别接受几个块。如果鼠标在NSTrackingArea中,则运行一个;如果鼠标不在NSTracking Area中,将运行一个。

@Michael提供了一个很好的答案,解决了我的问题。但有一件事,

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

我发现CGRectContainsPoint在我的盒子里有效,而不是CGPointInRect

最新更新