【问题标题】:mouseExited isn't called when mouse leaves trackingArea while scrolling当鼠标在滚动时离开 trackingArea 时,不会调用 mouseExited
【发布时间】:2012-02-17 06:36:07
【问题描述】:

为什么鼠标通过滚动或做动画退出 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];
}

更多细节:

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

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

【问题讨论】:

  • 几乎没有什么需要考虑的。什么是超类?您是否在不发送 super 的情况下覆盖任何超类方法?然后,这里是我总是传递给 trackingArea 以确保鼠标真的总是被跟踪的选项: NSTrackingMouseEnteredAndExited|NSTrackingMouseMoved|NSTrackingActiveInKeyWindow
  • @Dimillian77 我更改为“NSTrackingMouseEnteredAndExited|NSTrackingMouseMoved|NSTrackingActiveInKeyWindow”,但这并没有帮助......同样的问题。如果没有“NSTrackingActiveAlways”,它根本不起作用。我更新了我的问题,让它更清晰。
  • 您需要致电[super updateTrackingAreas]。这段代码是在 NSViews 子类还是 NSScrollView 里面?
  • @MarceloAlves 我没有调用它,它会在我滚动时自动调用。

标签: objective-c macos cocoa nstrackingarea


【解决方案1】:

正如您在问题标题中指出的那样,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:owner:userData:assumeInside 的assumeInside 参数:

如果是,第一个事件将在光标离开aRect时生成,无论添加跟踪矩形时光标是否在aRect内。如果为 NO,则如果光标最初位于 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 中则运行一个,如果不在则运行一个。

【讨论】:

  • 感谢您的明确回答!我刚刚更新了您的代码的一些行,它运行良好!所以现在我可以用我的赏金和 +1 奖励你了,谢谢!
  • 非常感谢!拯救了我的一天。我犯的最大错误是,假设 NSTrackingInVisibleRect 会像stackoverflow.com/a/4137243/804616 所说的那样处理它,这对于 NSOutlineView 来说似乎不够好(没有尝试过一个孤立的例子来检查它是否按预期运行) .
  • -updateTrackingAreas 似乎并没有在滚动视图的所有后代上被调用。 (我试图让它适用于 NSTableCellView 的子视图。)关于如何解决这个问题的任何想法?
  • @mdiep 这里有同样的问题,这个解决方案似乎不适用于基于视图的表格视图。有什么解决办法吗?
  • 如果有人希望它与基于视图的表视图一起使用,他们需要在 NSScrollView 上监听 NSClipView 上的 NSViewBoundsDidChangeNotification。从那里,您应该更新表中每个视图的跟踪矩形。
【解决方案2】:

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

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

我发现 CGRectContainsPoint 在我的盒子里工作,而不是 CGPointInRect

【讨论】:

  • 您是正确的,CGPointInRect 不是标准函数。我的意思是使用“NSPointInRect”,它是 Foundation 的一部分。我建议在处理 NSPoints 时使用 NSPointInRect,在使用 CGPoint 时使用 CGRectContainsPoint,因为 NSRect 和 CGPoint 实际上是不同的结构。无论如何,感谢您指出错误。我已经更新了我的原始答案以更正它。
  • @MichaelBuckley NSPoint 和 CGPoint 有什么区别,我认为它们是相同的,只有一个用于可可,一个用于碳?
  • 再看一遍,这两个结构是一样的。谢谢你再次纠正我。从第一个版本开始,它们都在 OS X 中,唯一的区别是 NSPoint 是在 Foundation 中定义的,而 CGPoint 是在 ApplicationServices 中定义的。两者都不是严格的 Carbon,尽管 Carbon 使用 CGPoint。我实际上在考虑 NSRange 和 CFRange,它们可能是不同的。 NSRange 使用 NSUinteger 成员,CFRange 使用 CFIndex 成员。这两个结构根据架构可能相同,但不保证相同。
猜你喜欢
  • 1970-01-01
  • 2019-02-03
  • 1970-01-01
  • 1970-01-01
  • 2013-04-04
  • 1970-01-01
  • 2018-01-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多