Re: a mouse event problem on macOS


Graham Cox
 

The mouseDragged goes to the view that handled the mouseDown. Since that’s not the same view, you don’t get the mouseDragged.

But it’s pretty easy to fix this. On the mouseDown, create and show your overlay view. Then call its mouseDown from your mouseDown.

Within the overlay’s mouseDown, implement your own mouse tracking loop which keeps control until the mouse goes up. This approach is sufficiently general that I have a handy category on NSView to deal with it. It’s also a documented and officially supported way to do this.


typedef void(^GCEventTrackingBlock)( NSEvent* event, BOOL* shouldEndTracking );


@interface NSView (EventTrackingBlock)

- (void) trackMouseEvent:(NSEvent*) initialMouseDownEvent usingBlock:(GCEventTrackingBlock) eventBlock;
- (void) trackEvents:(NSEventMask) eventTypes usingBlock:(GCEventTrackingBlock) eventBlock;
- (void) trackEvents:(NSEventMask) eventTypes untilDate:(NSDate*) date dequeue:(BOOL) dequeue usingBlock:(GCEventTrackingBlock) eventBlock;

@end




@implementation NSView (EventTrackingBlock)


- (void) trackMouseEvent:(NSEvent*) initialMouseDownEvent usingBlock:(GCEventTrackingBlock) eventBlock
{
// designed to be called from a -mouseDown: method, the initial event is passed to the block, and then unless the block cancelled it, it will
// enter a tracking loop for all other LEFT mouse events.

BOOL shouldEndTracking = NO;

eventBlock( initialMouseDownEvent, &shouldEndTracking );

if( !shouldEndTracking )
{
// tracks left mouse events plus flagsChanged. Tracks mouseMoved events if the view is set to report them.

[self trackEvents:NSLeftMouseDownMask | NSLeftMouseDraggedMask | NSLeftMouseUpMask | NSMouseMovedMask | NSFlagsChangedMask usingBlock:eventBlock];
}
}




- (void) trackEvents:(NSEventMask) eventTypes usingBlock:(GCEventTrackingBlock) eventBlock
{
[self trackEvents:eventTypes untilDate:[NSDate distantFuture] dequeue:YES usingBlock:eventBlock];
}


- (void) trackEvents:(NSEventMask) eventTypes untilDate:(NSDate*) date dequeue:(BOOL) dequeue usingBlock:(GCEventTrackingBlock) eventBlock
{
BOOL shouldEndTracking = NO;

while( !shouldEndTracking )
{
NSEvent* event = [self.window nextEventMatchingMask:eventTypes untilDate:date inMode:NSEventTrackingRunLoopMode dequeue:dequeue];
[self.window discardEventsMatchingMask:NSAnyEventMask beforeEvent:event];

// if a timeout occurred and event is nil, the loop will end after invoking the block unless the block resets the flag to NO.

if( event == nil )
shouldEndTracking = YES;

eventBlock( event, &shouldEndTracking );
}
}

@end

The event tracking block needs to look at event.type to see whether it’s a drag, mouse up, flags changed, etc. Usually you do that in a switch statement.


—Graham

On 20 Sep 2017, at 10:54 am, James Walker <list2@jwwalker.com> wrote:

In response to a mouse-down event, I want to display an overlay window and have a view in that window process mouseDragged messages. However, it does not get any mouseDragged messages, however much I wiggle the mouse. If I let the mouse button up and click again, I can then get a mouseDragged, but that's not the user interface I'm going for. I suppose it has something to do with the fact that my original mouseDown was not in the view in my overlay. I tried making a fake NSLeftMouseDown event and calling -[NSApplication sendEvent:], but that didn't help. Any ideas?

Join cocoa@apple-dev.groups.io to automatically receive all group messages.