当鼠标通过滚动或动画从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的视图中添加了NSView作为子视图。每个NSView都有自己的跟踪区域,当我滚动我的scrollView并离开跟踪区域“mouseExited”时没有被调用但没有滚动一切正常。问题是,当我滚动“updateTrackingAreas”被调用时,我认为这会产生问题。
* 与NSView相同的问题而不将其添加为子视图,因此这不是问题。
答案 0 :(得分: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
此代码存在两个问题。首先,当调用-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];
}
}
第二个问题是滚动。滚动或移动视图时,我们需要在该视图中重新计算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 :(得分:4)
@Michael提供了一个很好的答案,并解决了我的问题。但有一件事,
if (CGRectContainsPoint([self bounds], mouseLocation))
{
[self mouseEntered: nil];
}
else
{
[self mouseExited: nil];
}
我发现CGRectContainsPoint
在我的框中有效,而不是CGPointInRect
,