鼠标在滚动时离开trackingArea时不会调用mouseExited

Jus*_*Boo 29 macos cocoa objective-c nstrackingarea

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

我创建这样的代码:

鼠标进入和退出:

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

-(void)mouseExited:(NSEvent *)theEvent
{
    NSLog(@"Mouse exited");
}
Run Code Online (Sandbox Code Playgroud)

跟踪区域:

-(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];
}
Run Code Online (Sandbox Code Playgroud)

更多细节:

我在NSScrollView的视图中添加了NSView作为子视图.每个NSView都有自己的跟踪区域,当我滚动我的scrollView并离开跟踪区域"mouseExited"时没有调用但没有滚动一切正常.问题是,当我滚动"updateTrackingAreas"被调用时,我认为这会产生问题.

*仅使用NSView同样的问题而不将其添加为子视图,因此这不是问题.

Mic*_*ley 66

正如您在问题标题中所述,只有在鼠标移动时才会调用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
Run Code Online (Sandbox Code Playgroud)

这段代码有两个问题.首先,当调用-awakeFromNib时,如果鼠标已经在视图中,则不会调用-mouseEntered.这意味着即使鼠标在视图上方,背景仍然是白色的.实际上在NSView文档中提到了-addTrackingRect的assumeInside参数:owner:userData: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];
    }
}
Run Code Online (Sandbox Code Playgroud)

第二个问题是滚动.滚动或移动视图时,我们需要在该视图中重新计算NSTrackingAreas.这是通过删除跟踪区域然后重新添加它们来完成的.如您所述,滚动视图时会调用-updateTrackingAreas.这是删除和重新添加区域的地方.

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

这应该照顾你的问题.不可否认,每次添加跟踪区域时需要找到鼠标位置然后将其转换为视图坐标都会很快变旧,因此我建议在NSView上创建一个自动处理此类别的类别.您将无法始终调用[self mouseEntered:nil]或[self mouseExited:nil],因此您可能希望使类别接受几个块.如果鼠标在NSTrackingArea中,则运行一个,如果不在,则运行一个.

  • 如果有人希望它与基于视图的表视图一起使用,则他们需要在NSScrollView上侦听NSClipView上的NSViewBoundsDidChangeNotification。从那里,您应该为表中的每个视图更新跟踪矩形。 (4认同)
  • 谢谢你的明确非常好的答案!我刚刚更新了一些代码行,它完美无缺!所以现在我可以给你赏金和+1,谢谢! (3认同)
  • -updateTrackingAreas似乎并未在滚动视图的所有后代上调用。(我正在尝试使其用于NSTableCellView的子视图。)有关如何解决该问题的任何想法? (2认同)
  • @mdiep在这里遇到同样的问题,这个解决方案似乎不适用于基于视图的表视图.任何解决方案的想法? (2认同)