views:

672

answers:

2

Hello!

I'm looking for a straightforward way to make a Swing component forward all received events to its parent container (or even all parents up to root).

EDIT:
Where do I need this? I have a diagram editor. Components must forward key press and mouse clicks (to set themselves as "active" as soon as the user clicks a subelement of that component).

First, let me present my existing solution for this. It's a bit of a workaround.

public interface IUiAction {
 void perform(Component c);
}

public static void performRecursiveUiAction(Container parent, IUiAction action) {
 if (parent == null) {
  return;
 }

 for (Component c : parent.getComponents()) {
  if (c != null) {
   action.perform(c);
  }
 }

 for (Component c : parent.getComponents()) {
  if (c instanceof Container) {
   performRecursiveUiAction((Container) c, action);
  }
 }
}

/**
* 1) Add listener to container and all existing components (recursively).
* 2) By adding a ContainerListener to container, ensure that all further added
* components will also get the desired listener.
*
* Useful example: Ensure that every component in the whole component
* tree will react on mouse click.
*/
public static void addPermanentListenerRecursively(Container container,
  final IUiAction adder) {

 final ContainerListener addingListener = new ContainerAdapter() {
  @Override
  public void componentAdded(ContainerEvent e) {
   adder.perform(e.getChild());
  }
 };

 // step 1)
 performRecursiveUiAction(container, adder);

 // step 2)
 performRecursiveUiAction(container, new IUiAction() {
  @Override
  public void perform(Component c) {
   if (c instanceof Container) {
    ((Container) c).addContainerListener(addingListener);
   }
  }
 });
}

Usage:

addPermanentListenerRecursively(someContainer,
  new IUiAction(
    @Override
    public void perform(Component c){
      c.addMouseListener(somePermanentMouseListener);
    }
  )
);

By looking over the code, would you say it's a good concept?
The problem with my current concept is: It's forwarding only events, for which a listener was specified manually.

Can you suggest a better one?

+1  A: 

Based on your scenario I have a suggestion for the keyboard side of things:

You could use the KeyStroke facility of swing for that:

JRootPane rp = getRootPane();

KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0, false);
rp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(ks, "F2");
rp.getActionMap().put("F2", new AbstractAction() { 
public void actionPerformed(ActionEvent e) { onF2Action(); } });

This way you can register 'global' event handlers for shortcuts1.

1 although it might have some limitations in your case.

For the mouse-event case I would create a recursive function, which adds a MouseAdapter instance to every targeted component. For example:

void addToAll(Container c, MouseAdapter a) {
    for (Component p : c.getComponents()) {
        if (p instanceof InterrestingComponent) {
            p.addMouseListener(a);
            p.addMouseMotionListener(a);
            p.addMouseWheelListener(a);
        } else
        if (p instanceof Container) {
            addToAll((Container)p, a);
        }
    }
}

Just to capture the concept. You might need different or multiple receivers for various components.

Edit: Sorry, I accidentally said WindowAdapter instead of MouseAdapter.

kd304
On a sidenote: One of the good things about Swing in contrary to Delphi that it makes you easy to inject multiple event handlers practivally everywhere you want, whereas Delphi's GUI model was based on single the onSomething approach in my time.
kd304
+1  A: 

This looks like it would get a bit tricky if every component in your view handled the same mouse event. ie, if the user drags item 1, item 2 would process those events as well? if i understand correctly, you are looking to have this and this.parent and this.parent.parent handle mouse actions on this. in that case the recursion would be up, and not down.

you could create an Interface like this:

public interface MyInterface() {
   public void performSpecialAction(Event event);
}

you then have your containers implement this interface. your components would then need to incorporate this call to their appropriate event handling:

public static void performRecursiveUiAction(Component comp, Event event) {
  if (comp.getParent() == null) {
     return;
  }
  if (comp.getParent() instanceof MyInteface) {
     ((MyInterface)comp.getParent()).performSpecialAction(event);
  }
  ThisUtility.performRecursiveUiAction(comp.getParent(), event);

}

akf
yes, it would be a upward recursion.
ivan_ivanovich_ivanoff