4

Ok, I have one class (let's call it: MenuBarClass) that contain multiple Menu and MenuItem. I whant assign to every MenuItem an actionlistener, but.. instead of doing something like:

menuitem_1.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) {} });
menuitem_2.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) {} });
menuitem_3.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) {} });
// ...
menuitem_N.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) {} });

I whant my code more mantainable and.. more important.. I don't whant a lots of "if" in one huge ActionListener class like:

public void actionPerformed(ActionEvent e) {
  if (e.getSource().equals(menuitem_1)) {
    //do stuff..
  } else if (e.getSource().equals(menuitem_2)) {
    //do stuff..
  } else ...
}

how can i do this, if it is possible? Can anyone help?

dee-see
  • 21,841
  • 5
  • 54
  • 87
Andrea Rastelli
  • 577
  • 2
  • 10
  • 25
  • I think you maybe able to use the command pattern here. – adarshr Apr 22 '12 at 20:43
  • i don't know what a command pattern are or how i can use it in this situation (or any other). Can you help me? – Andrea Rastelli Apr 22 '12 at 20:48
  • No, sorry, that's Java for you. The syntax from your first code sample is the best you can get, assuming that there is no common code to reuse between the handlers of individual menu items. – Marko Topolnik Apr 22 '12 at 20:51
  • I can't belive that if I have something like 100 different buttons, there's no one single solution that I can use to improve the maintainability of my code, in Java. – Andrea Rastelli Apr 22 '12 at 20:54
  • The [command pattern](http://en.wikipedia.org/wiki/Command_pattern) could probably be used here. However, you would have to create a command object for each button, and its usefulness may be negligible. – Jeffrey Apr 22 '12 at 21:01
  • @AndreaRastelli: come on now, there are many such possible solutions, but you have to experiment and try. For more on Command pattern, look [here](http://blue-walrus.com/?p=552). You could create AbstractActions for each distinct action and pass those in to your menus as well. I've also sometimes created one or two "Control" classes, and have used anonymous listeners to simply call the correct control's method. – Hovercraft Full Of Eels Apr 22 '12 at 21:02
  • can you esplain more? With examples or links? Actions is not the ActionListener i suppose.. – Andrea Rastelli Apr 24 '12 at 08:13

4 Answers4

2

You can reduce verbosity using the reflection API to create a utility method:

package demo;    
import java.awt.event.*;
import java.lang.reflect.*;

public class ListenerProxies {    
  private static final Class<?>[] INTERFACES = { ActionListener.class };

  public static ActionListener actionListener(final Object target,
                                                    String method) {
    final Method proxied = method(target, method);
    InvocationHandler handler = new InvocationHandler() {
      @Override
      public Object invoke(Object proxy, Method method, Object[] args)
          throws Throwable {
        ActionEvent event = (ActionEvent) args[0];
        return proxied.invoke(target, event);
      }
    };
    return (ActionListener) Proxy.newProxyInstance(target.getClass()
        .getClassLoader(), INTERFACES, handler);
  }

  private static Method method(Object target, String method) {
    try {
      return target.getClass().getMethod(method, ActionEvent.class);
    } catch (NoSuchMethodException e) {
      throw new IllegalStateException(e);
    } catch (SecurityException e) {
      throw new IllegalStateException(e);
    }
  }
}

This can be utilized like this:

package demo;
import static demo.ListenerProxies.actionListener;
import java.awt.event.ActionEvent;
import javax.swing.*;

public class Demo {

  public static void main(String[] args) {
    Demo test = new Demo();
    JButton hello = new JButton("Say Hello");
    hello.addActionListener(actionListener(test, "sayHello"));
    JFrame frame = new JFrame();
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.getContentPane().add(hello);
    frame.pack();
    frame.setVisible(true);
  }

  public void sayHello(ActionEvent event) {
    System.out.println("Hello");
  }
}

The downside of this is the lack of compile-time checking that the sayHello(ActionEvent) method exists.

The performance costs will be negligible.

McDowell
  • 102,869
  • 29
  • 193
  • 261
1

Actually, those ActionListener objects are command objects by the means of the command design pattern. You may create custom subclasses instead of anonymous subclasses and gain a little bit more elegancy.

Now if the thing that bothers you is how you're wiring the action listeners with the command objects, I would do something like this using reflection:

  • Create a custom annotation something like @MenuAction which may take the class of the proper command object.
  • Create a generic action listener that reads, instantiate and execute this command.
  • Add to all the menu items the generic action listener.

If you think it well, you could create a framework and use this generic approach in multiple projects, but it would be a lot more work than simply wiring a couple of menu items with the proper ActionListener implementation by hand.

1

If the stuff you want to do is similar for every menu item you can create a class implementing ActionListener that takes constructor arguments. For example if each menu item should open a JFrame you can do something like this:

public class OpenFrameAction implements ActionListener
{
    private final JFrame frame;

    public OpenFrameAction(final JFrame frameToOpen)
    {
        this.frame = frameToOpen;
    }

    public void actionPerformed(ActionEvent e)
    {
        this.frame.setVisible(true);
    }
}

And then for each menu item:

menuitem_1.addActionListener(new OpenFrameAction(myFrameForMenuItem1));
siegi
  • 4,769
  • 1
  • 28
  • 40
0

Expanding on siegi's answer. You would only really want to add individual listeners to each item if the actions to be performed have noting in common (jump off a cliff, do the tango, have a cup of coffee). If this is the case you can't expect Java to perform any maintainability magic for you.

The more usual case is that the actions do have something in common (do the tango, do the foxtrot, etc). If this is the case you can follow siegi's advice or attach a listener to the menu (not the item). The event should tell you which item was selected and you can use that in your listener:

// something like this
actionPerformed(ActionEvent e)
{
    this.doDance(e.getSource().getSelectedValue());
}
barry
  • 201
  • 1
  • 4
  • If I'm not wrong, `e.getSource()` returns an `Object`, so you have to cast to get to the `getSelectedValue()` method. ;-) – siegi Apr 22 '12 at 21:33
  • 1
    You're probably not wrong. My point was that the event object should contain the required information. – barry Apr 22 '12 at 21:41