Re: a mouse event problem on macOS

James Walker

On 9/19/2017 6:43 PM, Graham Cox wrote:
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.

Thank you very much.  Your code didn't work for me at first, perhaps because of a complication that I did not mention in my original message:  The original mouse down happens in a Carbon window.  Anyway, the mouse events returned by -[NSWindow nextEventMatchingMask:untilDate:inMode:dequeue:] are for the wrong window and hence have the wrong mouse coordinates, so I had to convert the coordinates and create a new NSEvent for the right window before forwarding it on to my view's mouseDragged: method.  But it's working now.

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;


@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 );


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.


On 20 Sep 2017, at 10:54 am, James Walker <list2@...> 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 to automatically receive all group messages.