Mouse-Tracking and Cursor-Update Events
Mouse-tracking messages are sent to an object when the mouse pointer (without a mouse button being pressed) enters and exits a region of a window. This region is known as a tracking rectangle or tracking area. Mouse tracking enables the view owning the region to respond, for example, by drawing a highlight color or displaying a tool tip. Cursor-update events are a special kind of mouse-tracking event that the Application Kit handles automatically. When the mouse pointer enters a cursor rectangle, the Application Kit displays a cursor image appropriate to the type of view under the rectangle; for example, when a mouse pointer enters a text view, an I-beam cursor is displayed instead.
The sections in this chapter describe how you set up tracking rectangles and respond to mouse-tracking events. They also discuss how to specify and manage the rectangles for cursor-update events.
Handling Mouse-Tracking Events
A region of a view set up for tracking mouse movement is known as a tracking rectangle. When the mouse cursor enters the tracking rectangle, the Application Kit sends mouse-entered events (type NSMouseEntered
) to the object owning the rectangle (which is not necessarily the view itself); when the cursor leaves the rectangle, the Application Kit sends the object mouse-exited events (type NSMouseExited
). These events correspond to the mouseEntered:
and mouseExited:
methods, respectively, of NSResponder
. Mouse tracking can be useful for such tasks as displaying context-sensitive messages or highlighting graphic elements under the cursor. An NSView
object can have any number of tracking rectangles, which can overlap or be nested one within the other; the NSEvent
objects generated to represent mouse-tracking events include a tag (accessed through the trackingNumber
method) that identifies the rectangle associated with an event.
To create a tracking rectangle, send a addTrackingRect:owner:userData:assumeInside:
message to the NSView
object associated with the rectangle, as shown in Managing a Tracking-Area Object. This method registers an owner for the tracking rectangle, so that the owner receives the tracking-event messages. The owner is typically the view object itself, but need not be. The method returns the tracking rectangle’s tag so that you can store it for later reference in the event-handling methods mouseEntered:
and mouseExited:
. To remove a tracking rectangle, use the removeTrackingRect:
method, which takes as an argument the tag of the tracking rectangle to remove.
Listing A-1 Adding a tracking rectangle to a view region
- (void)viewDidMoveToWindow { |
// trackingRect is an NSTrackingRectTag instance variable |
// eyeBox is a region of the view (instance variable) |
trackingRect = [self addTrackingRect:eyeBox owner:self userData:NULL assumeInside:NO]; |
} |
In the above example, the custom view adds the tracking rectangle in the viewDidMoveToWindow
method instead of initWithFrame:
. Although NSView
implements the addTrackingRect:owner:userData:assumeInside:
method, a view’s window maintains the list of tracking rectangles. When a view’s initWithFrame:
initializer is invoked, the view is not yet associated with a window, so the tracking rectangle cannot yet be added to the window’s list. Thus the best place to add tracking rectangles initially is in the viewDidMoveToWindow
method.
Tracking rectangle bounds are inclusive for the top and left edges, but not for the bottom and right edges. Thus, if you have a unflipped view with a tracking rectangle covering its bounds, and the view’s frame has the geometry frame.origin = (100, 100), frame.size = (200, 200)
, then the area for which the tracking rectangle is active is frame.origin = (100, 101), frame.size = (199, 199
), in frame coordinates.
Tracking rectangles can also be used to provide NSMouseMoved
events to views in the mouseMoved:
method. For a view to receive NSMouseMoved
events, however, two things must happen:
The view must be the first responder.
The view’s window must be sent a
setAcceptsMouseMovedEvents:
message with an argument ofYES
.
As noted in Event Objects and Types and Handling Mouse Events, an NSWindow
object by default does not receive NSMouseMoved
events because they can easily flood the event queue. If you only want to receive mouse-moved messages while the mouse is over your view, you should turn them off again when a mouse-tracking session completes.
You typically send the setAcceptsMouseMovedEvents:
message (with an argument of YES
) in your implementation of mouseEntered:
. If you want to turn them off after a tracking session ends, you can send the message again with an argument of NO
in your implementation of mouseExited:
. However, you should also set the window state back to what it was before you turned on mouse-moved events to ensure that the window does not stop receiving mouse-moved events if it wants them for other purposes.
The tracking code in Listing 6-2 is used in making an “eyeball” follow the movement of the mouse pointer when it enters a tracking rectangle. Note that the mouseEntered:
implementation uses the wasAcceptingMouseEvents
instance variable to capture the window’s current state as regards mouse-moved events before these events are turned on for the current tracking session; later, in mouseExited:
, the value of this instance variable is used as the argument to setAcceptsMouseMovedEvents:
, thereby resetting window state.
Listing A-2 Handling mouse-entered, mouse-moved, and mouse-exited events
- (void)mouseEntered:(NSEvent *)theEvent { |
wasAcceptingMouseEvents = [[self window] acceptsMouseMovedEvents]; |
[[self window] setAcceptsMouseMovedEvents:YES]; |
[[self window] makeFirstResponder:self]; |
NSPoint eyeCenter = [self convertPoint:[theEvent locationInWindow] fromView:nil]; |
eyeBox = NSMakeRect((eyeCenter.x-10.0), (eyeCenter.y-10.0), 20.0, 20.0); |
[self setNeedsDisplayInRect:eyeBox]; |
[self displayIfNeeded]; |
} |
- (void)mouseMoved:(NSEvent *)theEvent { |
NSPoint eyeCenter = [self convertPoint:[theEvent locationInWindow] fromView:nil]; |
eyeBox = NSMakeRect((eyeCenter.x-10.0), (eyeCenter.y-10.0), 20.0, 20.0); |
[self setNeedsDisplayInRect:eyeBox]; |
[self displayIfNeeded]; |
} |
- (void)mouseExited:(NSEvent *)theEvent { |
[[self window] setAcceptsMouseMovedEvents:wasAcceptingMouseEvents]; |
[self resetEye]; |
[self setNeedsDisplayInRect:eyeBox]; |
[self displayIfNeeded]; |
} |
Because tracking rectangles are maintained by NSWindow
objects, a tracking rectangle is a static entity; it doesn’t move or change its size when an NSView
object does. If you use tracking rectangles, you should be sure to remove and reestablish them when you change the frame rectangle of the view object that contains them. If you’re creating a custom subclass of NSView
, you can override the setFrame:
and setBounds:
methods to do this, as shown in Compatibility Issues. If your class is not a custom view class, you can register your class instance as an observer for the notification NSViewFrameDidChangeNotification
and have it reestablish the tracking rectangles on receiving the notification.
Listing A-3 Resetting a tracking rectangle
- (void)setFrame:(NSRect)frame { |
[super setFrame:frame]; |
[self removeTrackingRect:trackingRect]; |
[self resetEye]; |
trackingRect = [self addTrackingRect:eyeBox owner:self userData:NULL assumeInside:NO]; |
} |
- (void)setBounds:(NSRect)bounds { |
[super setBounds:bounds]; |
[self removeTrackingRect:trackingRect]; |
[self resetEye]; |
trackingRect = [self addTrackingRect:eyeBox owner:self userData:NULL assumeInside:NO]; |
} |
You should also remove the tracking rectangle when your view is removed from its window, which can happen either because the view is moved to a different window, or because the view is removed as part of deallocation. One place to do this is the viewWillMoveToWindow:
method, as shown in Compatibility Issues.
Listing A-4 Removing a tracking rectangle when a view is removed from its window
- (void)viewWillMoveToWindow:(NSWindow *)newWindow { |
if ( [self window] && trackingRect ) { |
[self removeTrackingRect:trackingRect]; |
} |
} |
Managing Cursor-Update Events
One common use of tracking rectangles is to change the cursor image over different types of graphic elements. Text, for example, typically requires an I-beam cursor. Changing the cursor is such a common operation that NSView
defines several convenience methods to ease the process. A tracking rectangle generated by these methods is called a cursor rectangle. The Application Kit itself assumes ownership of cursor rectangles, so that when the user moves the mouse over the rectangle the cursor automatically changes to the appropriate image. Unlike general tracking rectangles, cursor rectangles may not partially overlap. They may, however, be completely nested, one within the other.
Because cursor rectangles need to be reset often as a view’s size and graphic elements change, NSView
defines a single method, resetCursorRects
, that’s invoked any time its cursor rectangles need to be reestablished. A concrete subclass overrides this method, invoking addCursorRect:cursor:
for each cursor rectangle it wishes to set (as illustrated in Listing A-5). Thereafter, the view’s cursor rectangles can be rebuilt by invoking the NSWindow
method invalidateCursorRectsForView:
. Before resetCursorRects
is invoked, the owning view is automatically sent a disableCursorRects
message to remove existing cursor rectangles.
Listing 6-4 shows an implementation of resetCursorRects
.
Listing A-5 Resetting a cursor rectangle
-(void)resetCursorRects |
{ |
[self addCursorRect:[self calculatedItemBounds] cursor:[NSCursor openHandCursor]]; |
} |
Although you can temporarily remove a single cursor rectangle with removeCursorRect:cursor:
, you should rarely need to do so. Whenever cursor rectangles need to be rebuilt, NSView
invokes resetCursorRects
so that you can establish only the cursor rectangles needed. If you implement resetCursorRects
in this way, you can then simply modify the state this method uses to build its cursor rectangles and then invoke the NSWindow
method invalidateCursorRectsForView:
.
An NSView
object’s cursor rectangles are automatically reset whenever:
Its frame or bounds rectangle changes, whether by a
setFrame...
orsetBounds...
message or by autoresizing.Its window is resized. In this case all of the window’s view objects get their cursor rectangles reset.
It is moved in the view hierarchy.
It is scrolled in an
NSScrollView
orNSClipView
object.
You can temporarily disable all the cursor rectangles in a window using the NSWindow
method disableCursorRects
and enable them again with the enableCursorRects
method. The areCursorRectsEnabled
method of NSWindow
tells you whether they’re currently enabled.
Copyright © 2016 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2016-09-13