37

マウスがスクロールまたはアニメーションを実行して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だけで同じ問題が発生するため、問題はありません。

4

2 に答える 2

76

質問のタイトルで述べたように、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

このコードには2つの問題があります。まず、-awakeFromNibが呼び出されたときに、マウスがすでにビュー内にある場合、-mouseEnteredは呼び出されません。これは、マウスがビューの上にある場合でも、背景が白のままであることを意味します。これは、NSViewのドキュメントで-addTrackingRect:owner:userData:assumeInsideのassumeInsideパラメーターについて実際に言及されています。

YESの場合、トラッキング長方形が追加されたときにカーソルが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];
    }
}

2番目の問題はスクロールです。ビューをスクロールまたは移動するときは、そのビューのNSTrackingAreasを再計算する必要があります。これは、追跡領域を削除してから再度追加することで実行されます。前述のように、ビューをスクロールすると-updateTrackingAreasが呼び出されます。これは、領域を削除して再度追加する場所です。

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

そして、それはあなたの問題の世話をする必要があります。確かに、追跡領域を追加するたびにマウスの位置を見つけてそれをビュー座標に変換する必要があるのはすぐに古くなるので、これを自動的に処理するカテゴリをNSViewに作成することをお勧めします。[self mouseEntered:nil]または[self mouseExited:nil]を常に呼び出すことができるとは限らないため、カテゴリに2、3ブロックを受け入れさせることができます。1つはマウスがNSTrackingAreaにある場合に実行し、もう1つはそうでない場合に実行します。

于 2012-02-02T04:35:34.143 に答える
4

@Michaelは素晴らしい答えを提供し、私の問題を解決しました。しかし、1つあります。

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

CGRectContainsPoint私は自分の箱の中に作品を見つけましたがCGPointInRect

于 2012-10-03T14:05:33.420 に答える