How do I pass an event up the component hierarchy?
I have a child component which has a mouse listener and a parent component with a mouse listener. However the child component consumes all the mouse events so the parent doesn't get them.
Is there a way for a child component to receive mouse events, then decided to pass them back up the component hierarchy so the parent can still receive them too?
I've spent the last two hours on Google and can't seem to find anything relevant.
Cheers, Eric
# 1
Don't let the child component consume the mouse event, and it will automatically be passed up the hierarchy to it's parent, and so on until someone consumes it.
# 2
> Don't let the child component consume the mouse> event, and it will automatically be passed up the> hierarchy to it's parent, and so on until someone> consumes it.But how do I prevent the child component from consuming the event?
# 3
You will need to override this method which is in java.awt.Component and make sure it does not call consume() on the mouse events that you want propagated upwards.
protected void processMouseEvent(MouseEvent e)
{
if (mouseListener == null)
return;
switch (e.id)
{
case MouseEvent.MOUSE_CLICKED:
mouseListener.mouseClicked(e);
break;
case MouseEvent.MOUSE_ENTERED:
mouseListener.mouseEntered(e);
break;
case MouseEvent.MOUSE_EXITED:
mouseListener.mouseExited(e);
break;
case MouseEvent.MOUSE_PRESSED:
mouseListener.mousePressed(e);
break;
case MouseEvent.MOUSE_RELEASED:
mouseListener.mouseReleased(e);
break;
}
e.consume();
}
# 4
Yup, that's what I've been trying to do, or more specifically
protected void processMouseEvent(MouseEvent e) {
if (e.getID() != MouseEvent.MOUSE_PRESSED);
super.processMouseEvent(e);
}
And variations on this, but no success.
Is this supposed to implemented on the child component or the parent component?
# 5
By the way, I meant to write
protected void processMouseEvent(MouseEvent e) {
if (e.getID() != MouseEvent.MOUSE_PRESSED)
super.processMouseEvent(e);
}
which still doesn't work.
The only way my parent component can get the mouse pressed event is if the mouse is not over the child component, or the child component is not registered as a mouse listener.
Somehow this has turned out to be much harder than I would have thought.
# 6
Maybe you can just use an AWTEventListener to listen for all events.
# 7
> Maybe you can just use an AWTEventListener to listen> for all events.How would I do that?
# 8
Not exactly sure but here is the basic format of using an AWTEventListener.
Toolkit.getDefaultToolkit().addAWTEventListener( new AWTEventListener()
{
public void eventDispatched(AWTEvent e)
{
System.out.println(e);
}
}, AWTEvent.MOUSE_MOTION_EVENT_MASK + AWTEvent.MOUSE_EVENT_MASK);
So maybe you look at the event to see if its consumed or not. If it is then you get the parent of the component the event was created for and then dispatch a new event to parent.
Or, even easier would be to handle the parent logic directly in the listener. Again you would check the source of the component. If the source is a child of your parent panel, you just do you code and don't even bother forwarding the event.
# 9
I've been digging into Sun's source code for Component (and friends) to see if I can figure out how event dispatching works.
/**
* Dispatches an event to this component or one of its sub components.
* Calls <code>processEvent</code> before returning for 1.1-style
* events which have been enabled for the <code>Component</code>.
* @param e the event
*/
public final void dispatchEvent(AWTEvent e) {
dispatchEventImpl(e);
}
but after reading dispatchEventImpl() I cannot see how events are dispatched to sub components. In particular, I cannot see how events are returned to the parents when they are not consumed by the children. Also, the whole consume() mechanism is riddled with special cases, and I still cannot figure out who calls consume() for MouseEvents. Overall the code is not at all elegant as I had hoped. Perhaps if I study Sun's code for a few more days I'll be able to figure out what it's doing, or even if it's working correctly - this just seems a lot of work to do something that should be so incredibly basic.
# 10
> Not exactly sure but here is the basic format of> using an AWTEventListener.Thanks, I'll try looking at that as well.
# 11
> I've been digging into Sun's source code for
> Component (and friends) to see if I can figure out
> how event dispatching works.
> but after reading dispatchEventImpl() I cannot see
> how events are dispatched to sub components.
I know how you feel :) I tried that too some time ago, and it cost me a lot of time and twice more headaches.
> In
> particular, I cannot see how events are returned to
> the parents when they are not consumed by the
> children. Also, the whole consume() mechanism is
> riddled with special cases
Yes. It's full of special cases, and that's why I also couldn't find out how many times an event is redispatched if it is not consumed (particularly, can one create and infinite dispatching loop, if one takes care to never consume an event?)
> [...] and I still cannot figure
> out who calls consume() for MouseEvents.
As far as I can see, this happens in Container.dispatchEventImpl:
void dispatchEventImpl(AWTEvent e) {
if ((dispatcher != null) && dispatcher.dispatchEvent(e)) {
e.consume();
if (peer != null) {
peer.handleEvent(e);
}
return;
}
The dispatcher.dispatchEvent() call is what figures out which component the event should go to. It rightfully determines that your child component should receive the event, sends it to it, and then the event is consumed. Unfortunately, you cannot override this method since it is package-private. I suppose you should override Component.dispatchEvent() instead, check your special case there, and then call the superclass method.
Also, your proposed overridden method
protected void processMouseEvent(MouseEvent e) {
if (e.getID() != MouseEvent.MOUSE_PRESSED);
super.processMouseEvent(e);
}
will not work, since it does NOTHING with mouse press events. It effectively discards them. You need to call mouseListener.mousePressed() when you have a mouse press event, just like the original method does.
Hope this helps you.
Greets,
Mike
MikePa at 2007-7-12 19:38:31 >

# 12
> As far as I can see, this happens in
> Container.dispatchEventImpl:
if ((dispatcher != null) && dispatcher.dispatchEvent(e)) {
e.consume();
if (peer != null) {
peer.handleEvent(e);
}
return;
}
Whoa! That's certainly not the way I would have implemented it! That's pretty short sighted and inflexible. Thanks for pointing me to the Container instead of the Component - I was going crazy.
> The dispatcher.dispatchEvent() call is what figures
> out which component the event should go to. It
> rightfully determines that your child component
> should receive the event, sends it to it, and then
> the event is consumed. Unfortunately, you cannot
> override this method since it is package-private. I
> suppose you should override
> Component.dispatchEvent() instead, check your
> special case there, and then call the superclass
> method.
Sadly this will not work because dispatchEvent is declared final. I really think the implementors of this could not have conceived of a child component wanting to pass an event back to the parent because they've done an excellent job of preventing it. I think I need to file another bug report.
>Hope this helps you.
>
>Greets,
>Mike
Helps me in spirit, but unfortunately does not solve my problem.
Thanks, Eric
# 13
> > Unfortunately, you cannot
> > override this method since it is package-private.
That was wrong of mine. Package-private members are visible to subclasses too (the package access is a complete superset of protected access). So you CAN override dispatchEventImpl and do your stuff there.
> I
> > suppose you should override
> > Component.dispatchEvent() instead, check your
> > special case there, and then call the superclass
> > method.
>
> Sadly this will not work because dispatchEvent is
> declared final.
Hm, I must have overseen that. Too many errors for one posting :)
I just wonder why they made dispatchEvent() final, when it just calls dispatchEventImpl(), and it is not final...
Another useful method is Component.enableEvents() and disableEvents() (which are both protected). You can use them to modify the so called event mask of a component, which is used to signify which events a Component is interested in. Disabled events (= not on the event mask) are not delivered to the component and the dispatcher skips it, looking for parent components which have enabled the event. For example, if you have a JButton in a scroll pane and you scroll the mouse wheel on the button, the event is dispatched to the scroll pane, even though it occurs over the surface of the button. That's because a button is not interested in mouse wheel events.
You can use that hack the following way: In the mouse listener for your child component, after you have done your work, disable mouse events for the component and then post the event again (invoke dispatchEvent(theEvent)). That way, the event should be delivered to the closest parent that is interested in mouse events. This is done synchronously, so after all listeners for the parent components have finished, the dispatchEvent() method returns in your listener for the child component. Then you re-enable mouse events on the child component in order to receive the next mouse event.
I don't know if this works or if it has any side effects, but it's worth a try...
Hope this helps you more :).
Greets,
Mike
MikePa at 2007-7-12 19:38:31 >

# 14
> > > Unfortunately, you cannot
> > > override this method since it is
> package-private.
>
> That was wrong of mine. Package-private members are
> visible to subclasses too (the package access is a
> complete superset of protected access). So you CAN
> override dispatchEventImpl and do your stuff there.
>
When I try that the compiler gives me the following warning
The method SheetElementPlacementComponent.dispatchEventImpl(AWTEvent) does not override the inherited method from Container since it is private to a different package.
It lets me define the method, but does not let me override it.
> I just wonder why they made dispatchEvent() final,
> when it just calls dispatchEventImpl(), and it is
> not final...
>
> Another useful method is Component.enableEvents()
> and disableEvents() (which are both protected). You
> can use them to modify the so called event mask of a
> component, which is used to signify which events a
> Component is interested in. Disabled events (= not
> on the event mask) are not delivered to the
> component and the dispatcher skips it, looking for
> parent components which have enabled the event. For
> example, if you have a JButton in a scroll pane and
> you scroll the mouse wheel on the button, the event
> is dispatched to the scroll pane, even though it
> occurs over the surface of the button. That's
> because a button is not interested in mouse wheel
> events.
>
> You can use that hack the following way: In the
> mouse listener for your child component, after you
> have done your work, disable mouse events for the
> component and then post the event again (invoke
> dispatchEvent(theEvent)). That way, the event should
> be delivered to the closest parent that is
> interested in mouse events. This is done
> synchronously, so after all listeners for the parent
> components have finished, the dispatchEvent() method
> returns in your listener for the child component.
> Then you re-enable mouse events on the child
> component in order to receive the next mouse event.
>
> I don't know if this works or if it has any side
> effects, but it's worth a try...
I can't call dispatchEvent() because it creates an infinite loop resulting in a stack overflow. It tried calling((JComponent) getParent()).dispatchEvent(e);
but this does not help either, the parent never gets the event.
>Hope this helps you more :).
>Greets,
> Mike
Sorry, I still can't find a way around it other than to redesign my UI.
FYI I created a bug report and Sun have assigned it Bug Id: 6571453. Everyone please vote on this if you think it's a cool feature request.
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6571453
Cheers, Eric
# 15
> > That was wrong of mine. Package-private members
> are
> > visible to subclasses too (the package access is a
> > complete superset of protected access).
Oh man, I totally messed that up... It's the other way round, protected access is a superset of package access, i.e. protected members are also package visible.
Well you still can define your class in the java.awt package in order to be able to override the method, but that's a very bad style... It should technically work, though...
> I can't call dispatchEvent() because it creates an
> infinite loop resulting in a stack overflow. It tried
> calling
> ((JComponent) getParent()).dispatchEvent(e);
> but this does
> not help either, the parent never gets the event.
Well, I forgot to mention that you should best post the event to the root of the hierarchy (or at least to the nearest heavyweight parent of your component). In a Swing GUI that's usually a JFrame, JDialog, etc. I could imagine that not every Swing component has a dispatcher, but I'm not sure about this.
What's for sure is that disabled components (ones that have setEnabled(false) called on them) do not receive events. Maybe you could look what that method does in order to achieve that.
> Sorry, I still can't find a way around it other than
> to redesign my UI.
I also had the problem a while ago and did not manage to get it working. I wasn't so aware of the event dispatching mechanism then, though. Unfortunately, I don't have the time now to test my proposals, they're just loud thoughts...
Greets,
Mike
MikePa at 2007-7-21 22:24:40 >

