当鼠标在滚动时离开跟踪区域时,不会调用 mouseExited

发布于 2024-12-28 17:00:49 字数 1064 浏览 0 评论 0原文

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

更多详细信息:

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

* 仅 NSView 存在同样的问题,而没有将其添加为子视图,因此这不是问题。

Why mouseExited/mouseEntered isn't called when mouse exits from NStrackingArea by scrolling or doing animation?

I create code like this:

Mouse entered and exited:

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

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

Tracking area:

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

More details:

I have added NSViews as subviews in NSScrollView's view. Each NSView have his own tracking area and when I scroll my scrollView and leave tracking area "mouseExited" isn't called but without scrolling everything works fine. Problem is that when I scroll "updateTrackingAreas" is called and I think this makes problems.

* Same problem with just NSView without adding it as subview so that's not a problem.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

活雷疯 2025-01-04 17:00:49

正如您在问题标题中所指出的, 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 参数中提到:

如果是YES,当添加跟踪矩形时,无论光标是否在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 中则运行一项,如果不在 NSTrackingArea 中则运行一项。

As you noted in the title of the question, mouseEntered and mouseExited are only called when the mouse moves. To see why this is the case, let's first look at the process of adding NSTrackingAreas for the first time.

As a simple example, let's create a view that normally draws a white background, but if the user hovers over the view, it draws a red background. This example uses 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

There are two problems with this code. First, when -awakeFromNib is called, if the mouse is already inside the view, -mouseEntered is not called. This means that the background will still be white, even though the mouse is over the view. This is actually mentioned in the NSView documentation for the assumeInside parameter of -addTrackingRect:owner:userData:assumeInside:

If YES, the first event will be generated when the cursor leaves aRect, regardless if the cursor is inside aRect when the tracking rectangle is added. If NO the first event will be generated when the cursor leaves aRect if the cursor is initially inside aRect, or when the cursor enters aRect if the cursor is initially outside aRect.

In both cases, if the mouse is inside the tracking area, no events will be generated until the mouse leaves the tracking area.

So to fix this, when we add the tracking area, we need to find out if the cursor is within in the tracking area. Our -createTrackingArea method thus becomes

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

The second problem is scrolling. When scrolling or moving a view, we need to recalculate the NSTrackingAreas in that view. This is done by removing the tracking areas and then adding them back in. As you noted, -updateTrackingAreas is called when you scroll the view. This is the place to remove and re-add the area.

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

And that should take care of your problem. Admittedly, needing to find the mouse location and then convert it to view coordinates every time you add a tracking area is something that gets old quickly, so I would recommend creating a category on NSView that handles this automatically. You won't always be able to call [self mouseEntered: nil] or [self mouseExited: nil], so you might want to make the category accept a couple blocks. One to run if the mouse is in the NSTrackingArea, and one to run if it is not.

看春风乍起 2025-01-04 17:00:49

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

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

我发现 CGRectContainsPoint 在我的框中起作用,而不是 CGPointInRect

@Michael offers a great answer, and solved my problem. But there is one thing,

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

I found CGRectContainsPoint works in my box, not CGPointInRect,

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文