Mouse Events Bleeding Through NSView - cocoa

I have an NSView which covers its parent window's content view. This view has a click event handler which removes it from the content view. Inside this view, I have another view. When I drag the mouse in this inner view, the mouse events are applied not only to the view in the front, but also to the views behind. Additionally, the cursors from the views behind are showing up as well. This is the same problem occurring here: NSView overlay passes mouse events to underlying subviews? but the answer there won't work for my project because I can't open another window.

Without seeing your event-handling code it's difficult to know what's happening, but I suspect you might be calling super's implementation of the various event-handling methods in your implementations.
NSView is a subclass of NSResponder, so by default un-handled events are passed up the responder chain. The superview of a view is the next object in the responder chain, so if you call, for example, [super mouseDown:event] in your implementation of ‑mouseDown:, the event will be passed to the superview.
The fix is to ensure you don't call super's implementation in your event handlers.
This is incorrect:
- (void)mouseDown:(NSEvent*)anEvent
{
//do something
[super mouseDown:event];
}
This is correct:
- (void)mouseDown:(NSEvent*)anEvent
{
//do something
}

Rob's answer and Maz's comment on that answer solve this issue, but just to make it absolutely explicit. In order to prevent a NSView from bleeding it's mouse events to the parent, one must implement the empty methods.
// NSResponder =========================================
- (void) mouseDown:(NSEvent*)event {}
- (void) mouseDragged:(NSEvent*)event {}
- (void) mouseUp:(NSEvent*)event {}

Related

How to make a custom NSView within a NSMenuItem becomeFirstResponder?

I have a custom NSView within a NSMenuItem (attached to a MenuBar) that respond to a mouseDown event. But I need to click twice on the custom view for the mouseDown function to be called, this is because the custom view should be first responder. And when I override the method acceptsFirstResponder in my CustomView Controller as indicated by the Cocoa Event Handling Guide, it does not work. What is the solution? Is it doable?
Override the NSView method acceptsFirstMouse: to return YES for the event in question. If you only want to accept the first mouse click for some types of events, you can do that by examining the event parameter passed in. Unless there is something special about the NSMenuItem case in particular, this should be what you want; it's the standard Cocoa mechanism for this. Note that this method is not the same as the acceptsFirstResponder method you have tried. See Apple's doc for details.
For reference I have just added to my custom view the following:
- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent {
return YES;
}

adding delegate to responder chain stops scrolling from working

For a number of reasons, I have added my class that implements NSOutlineViewDelegate protocol to the responder chain:
[myOutlineView setNextResponder:self];
This stops my outline view from scrolling. Take the call out - scrolling works fine, put it back - scrolling stops. If I use the up and down arrows to move the selection through the view it scrolls to show the selected row OK, but gesture scrolling doesn't do anything.
The delegate contains quite a few methods for supporting drag and drop, and ibaction methods for supporting context menus, but I can't think what is in there that would interfere with scrolling (I am using a macbook air with gesture scrolling). Anyone got any ideas what is causing the interference? or any ideas how to diagnose?
I should add that I made the delegate class a subclass of NSResponder.
So the answer is, when adding a delegate into the responder chain, you must also add to the delegate the responder that used to be in its place - otherwise the chain gets broken and the events don't get handled, so it goes:
NSResponder *nextResponder = myOutlineView.nextResponder;
[myOutlineView setNextResponder:self];
[self setNextResponder:nextResponder];
With the responder chain restored, my outline view now scrolls again. Hooray

NSWindow tracking

I would like to track each time a certain window appears (becomes visible to the user) in a OS X app. Where would be the most adequate place to call the tracker?
windowWillLoad, maybe?
I expected to find something like windowWillAppear but it seems I'm thinking too much iOS.
How about getting notification such as NSWindowDidBecomeMainNotification, By main I guess the one which is top most on screen directly visible by user.
see : Apple Documentation
Yes, one would expect that a window would notify its delegate or its controller with a windowWillAppear or windowDidAppear message, or post a documented notification like NSWindowDidAppearNotification. But alas, none of those exist. I filed a bug report with Apple and was given the advice to use a storyboard and a view controller instead. This is unhelpful in legacy apps that already use a bunch of window controllers and xibs.
You could subclass NSWindow and override orderWindow:relativeTo: to send a notification. Most, but not quite all, of the messages that make a window show itself ultimately go through this method, including orderBack:, orderFront:, makeKeyAndOrderFront:, and -[NSWindowController showWindow:]. But orderFrontRegardless does not go through orderWindow:relativeTo:, so you would also want to override that for completeness.
Another way to be notified is to make a subclass of NSViewController that controls some view that's always visible in the window. The view controller will receive viewWillAppear and viewDidAppear.
If you're subclassing NSWindow or NSViewController already for some other reason, either of these is a reasonable solution.
If you're not subclassing NSWindow already, and don't have an NSViewController subclass for a view that's always visible in the window, then another way is to use Cocoa bindings to connect the window's visible binding to a property one of your objects. For example, I have a custom NSWindowController subclass. I gave it a windowIsVisible property:
#interface MyWindowController ()
#property (nonatomic) BOOL windowIsVisible;
#end
and I implemented the accessors like this:
- (BOOL)windowIsVisible { return self.window.visible; }
- (void)setWindowIsVisible:(BOOL)windowIsVisible {
NSLog(#"window %# became %s", self.window, windowIsVisible ? "visible" : "hidden");
}
and in awakeFromNib, I bind the window's visible binding to the property like this:
- (void)awakeFromNib {
[super awakeFromNib];
[self.window bind:NSVisibleBinding toObject:self withKeyPath:NSStringFromSelector(#selector(windowIsVisible)) options:nil];
}
When the window becomes visible, the setWindowIsVisible: setter is called with an argument of YES. Note that if the whole app is hidden and reappears, the setter is called again, even though it wasn't called with argument NO when the app was hidden. So be careful not to assume the window was previously hidden.
Also, the binding might create a retain cycle, so you should probably unbind it when the window is closed, unless you want to keep the window and controller around. Note that the window does post NSWindowWillCloseNotification when it's closing, so you don't need any special magic to detect that.

Hide child controls in an NSView

I have an NSView with multiple child controls in it. I know I can call [childControl setHidden:TRUE] but I was wondering if its possible to block the message "drawRect:" for the child controls.
Ive noticed that not calling [super drawRect:NSZeroRect] on the NSView does not affect the child controls. So my question is who calls the child controls drawRect message? And if there is a way to block it.
Thanks, Jose.
Every time the controls should react optically, they draw its view again.
If you really want to solve this problem like that, you can create for each control a subclass and add a code like this:
-(void)drawRect:(NSRect)rect {
if (!self.blocked) {
[super drawRect:rect];
}
}
The property "blocked" is a boolean which you have to set to YES or NO if you want to block it.
Note: To totally hide it the control subclass has to be blocked before it draws it self the first time.

NSButtonCell inside custom NSCell

in my cocoa application, I need a custom NSCell for an NSTableView. This NSCell subclass contains a custom NSButtonCell for handling a click (and two or three NSTextFieldCells for textual contents). You'll find a simplified example of my code below.
#implementation TheCustomCell
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView {
// various NSTextFieldCells
NSTextFieldCell *titleCell = [[NSTextFieldCell alloc] init];
....
// my custom NSButtonCell
MyButtonCell *warningCell = [[MyButtonCell alloc] init];
[warningCell setTarget:self];
[warningCell setAction:#selector(testButton:)];
[warningCell drawWithFrame:buttonRect inView:controlView];
}
The problem I'm stuck with is: what is the best/right way to get that Button (more precisely: the NSButtonCell) inside this NSCell to work properly? "work" means: trigger the assigned action message and show the alternate image when clicked. Out of the box, the button doesn't do anything when clicked.
Information / readings on this topic is hard to find. The only posts I found on the net pointed me to implementing
- (BOOL)trackMouse:(NSEvent *)theEvent inRect:(NSRect)cellFrame ofView:(NSView *)controlView untilMouseUp:(BOOL)untilMouseUp;
Is this the correct way to do it??? Implement trackMouse: in my containing NSCell? And then forward the event to the NSButtonCell? I would have expected the NSButtonCell itself to know what to do when it's being clicked (and I saw the trackMouse: methods more in cunjunction with really tracking mouse movements - not as a training wheel for 'standard' click behaviour). But it seems like it doesn't do this when included in a cell itself...
It seems I haven't grasped the big picture on custom cells, yet ;-)
I'd be glad if someone could answer this (or point me to some tutorial or the like) out of his own experience - and tell me if I'm on the right track.
Thanks in advance,
Tobi
The minimal requirements are:
After left mouse down on the button, it must appear pressed whenever the mouse is over it.
If the mouse then releases over the button, your cell must send the appropriate action message.
To make the button look pressed, you need to update the button cell's highlighted property as appropriate. Changing the state alone will not accomplish this, but what you want is for the button to be highlighted if, and only if, its states is NSOnState.
To send the action message, you need to be aware of when the mouse is released, and then use -[NSApplication sendAction:to:from:] to send the message.
In order to be in position to send these messages, you will need to hook into the event tracking methods provided by NSCell. Notice that all those tracking methods, except the final, -stopTracking:... method, return a Boolean to answer the question, "Do you want to keep receiving tracking messages?"
The final twist is that, in order to be sent any tracking messages at all, you need to implement -hitTestForEvent:inRect:ofView: and return an appropriate bitmask of NSCellHit... values. Specifically, if the value returned doesn't have the NSCellHitTrackableArea value in it, you won't get any tracking messages!
So, at a high level, your implementation will look something like:
- (NSUInteger)hitTestForEvent:(NSEvent *)event
inRect:(NSRect)cellFrame
ofView:(NSView *)controlView {
NSUInteger hitType = [super hitTestForEvent:event inRect:cellFrame ofView:controlView];
NSPoint location = [event locationInWindow];
location = [controlView convertPointFromBase:location];
// get the button cell's |buttonRect|, then
if (NSMouseInRect(location, buttonRect, [controlView isFlipped])) {
// We are only sent tracking messages for trackable areas.
hitType |= NSCellHitTrackableArea;
}
return hitType;
}
+ (BOOL)prefersTrackingUntilMouseUp {
// you want a single, long tracking "session" from mouse down till up
return YES;
}
- (BOOL)startTrackingAt:(NSPoint)startPoint inView:(NSView *)controlView {
// use NSMouseInRect and [controlView isFlipped] to test whether |startPoint| is on the button
// if so, highlight the button
return YES; // keep tracking
}
- (BOOL)continueTracking:(NSPoint)lastPoint at:(NSPoint)currentPoint inView:(NSView *)controlView {
// if |currentPoint| is in the button, highlight it
// otherwise, unhighlight it
return YES; // keep on tracking
}
- (void)stopTracking:(NSPoint)lastPoint at:(NSPoint)stopPoint inView:(NSView *)controlView mouseIsUp:(BOOL)flag {
// if |flag| and mouse in button's rect, then
[[NSApplication sharedApplication] sendAction:self.action to:self.target from:controlView];
// and, finally,
[buttonCell setHighlighted:NO];
}
The point of NSCell subclasses is to separate responsibility for rendering and handling common UI elements (the controls) from the visual- and event-hierarchy
responsibilities of the NSView classes. This pairing permits each one to provide greater specialization and variability without burdening the other. Look at the large number of NSButton instances one can create in Cocoa. Imagine the number of NSButton sub-classes that would exist if this split in functionality were absent!
Using design pattern language to describe the roles: an NSControl acts as a façade, hiding details of its composition from its clients and passing events and rendering messages to its NSCell instance which acts as a delegate.
Because your NSCell subclass includes other NSCell subclass instances within its composition, they no longer directly receive these event messages from the NSControl instance which is in the view hierarchy. Thus, in order for these cell instances to receive event messages from the event responder chain (of the view hierarchy), your cell instance needs to pass along those relevant events. You are recreating the work of the NSView hierarchy.
This isn't necessarily a bad thing. By replicating the behavior of NSControl (and its NSView superclass) but in an NSCell form, you can filter the events passed on to your sub-cells by location, event type, or other criteria. The drawback is replicating the work of NSView/NSControl in building the filtering & management mechanism.
So in designing your interface, you need to consider whether the NSButtonCell (and NSTextFieldCells) are better off in NSControls in the normal view hierarchy, or as sub-cells in your NSCell subclass. It's better to leverage the functionality which already exists for you in a codebase than to re-invent it (and continue maintaining it later) unnecessarily.

Resources