Sending an event to an NSControl underneath another NSControl if it does not handle it
I have multiple superpo开发者_StackOverflow中文版sed controls which can handle a mouse click under certain conditions. What I want to be able to do is:
- The top control receives the
mouseDown:
event. - The top control decides if it handles the
mouseDown:
event. - If it does, do something and prevent other controls from receiving the
mouseDown:
event. - If it does not, send the event to the control that's underneath.
- This control decides if it handles the event.
- etc.
In essence I'm trying to send the event to the control whose "Z-Order" is just below the top control, without the top control needing to know about the other controls or needing some special setup at instantiation.
The first thing that came to my mind was to send the event to [topControl nextResponder]
but it seems the nextResponder
for all controls on the window is the window itself and not a chain of controls based on their Z-Order as I previously thought.
Is there a way to do this without resorting to setting the next responder manually? The goal is to get a control which is independent from the other controls and can just be dropped on a window and work as expected.
Thanks in advance!
All you have to do is call [super mouseDown:event]
. Since Mac OS X 10.5 (this did not work the same way before) NSView knows how to handle overlapping views and will take care of event handling for you.
If you need to target releases before 10.5: This is a really bad idea. Not only does the event handling mechanism not know how to deal with overlapping subviews, neither does the drawing system and you can see some very strange artefacts. That said, if you're determined:
Override -[NSView hitTest:]
in your custom control/view. AppKit uses this method to determine which view in the hierarchy to deliver mouse events to. If you return nil
that point in your custom view is ignored and the event should get delivered to the next view covering that point.
Mind though, this is still a bad idea because of the reasons I outlined above. It just wasn't something formally supported by AppKit at the time. The more generally accepted workaround on 10.4 and earlier is to use a child window.
It's hard to know exactly the best approach because I don't know what your application does, but here's a thought. It sounds like you want to pass the messages up through the view hierarchy... somehow.
Regardless, a view would do one of two things:
- handle the message
- pass it to the "next view" (how you define "next view" depends on your application)
So. How would you do this? The default behavior for a view should be to pass the message to the next view. A good way of implementing this kind of thing is through an informal protocol.
@interface NSView (MessagePassing)
- (void)handleMouseDown:(NSEvent *)event;
- (NSView *)nextViewForEvent:(NSEvent *)event;
@end
@implementation NSView (MessagePassing)
- (void)handleMouseDown:(NSEvent *)event {
[[self nextView] handleMouseDown:event];
}
- (NSView *)nextViewForEvent:(NSEvent *)event {
// Implementation dependent, but here's a simple one:
return [self superview];
}
@end
Now, in the views that should have that behavior, you'd do this:
- (void)mouseDown:(NSEvent *)event {
[self handleMouseDown:event];
}
- (void)handleMouseDown:(NSEvent *)event {
if (/* Do I not want to handle this event? */) {
// Let superclass decide what to do.
// If no superclass handles the event, it will be punted to the next view
[super handleMouseDown:event];
return;
}
// Handle the event
}
You would probably want to create an NSView
subclass to override mouseDown:
that you would then base your other custom view classes on.
If you wanted to determine the "next view" based on actual z-order, keep in mind that z-order is determined by the order within the subviews
collection, with later views appearing first. So, you could do something like this:
- (void)nextViewForEvent:(NSEvent *)event {
NSPoint pointInSuperview = [[self superview] convertPoint:[event locationInWindow] fromView:nil];
NSInteger locationInSubviews = [[[self superview] subviews] indexOfObject:self];
for (NSInteger index = locationInSubviews - 1; index >= 0; index--) {
NSView *subview = [[[self superview] subviews] objectAtIndex:index];
if (NSPointInRect(pointInSuperview, [subview frame]))
return subview;
}
return [self superview];
}
This might be way more than you wanted, but I hope it helps.
精彩评论