tags:

views:

334

answers:

6

The naive way of writing building a menu in a Java Swing app is to do something like:

JMenu fileMenu = new JMenu("File");
JMenuItem openItem = new JMenuItem("Open...");
openItem.addActionListener(new ActionListener() { /* action listener stuff */ } )
fileMenu.addMenuItem(openItem);

A more experienced developer will recognize that actions can be accessed through a variety of mechanisms - menus, toolbar buttons, maybe even other workflows in the system. That person is more likely to write:

Action openAction = new AbstractAction();
openAction.setName("Open...");
openAction.addActionListener(new ActionListener() { /* action listener stuff */ } )
...
JMenuItem openItem = new JMenuItem(openAction);

My question is, what is the best way to manage these Action objects so they can be used across menus, toolbars, etc?

  • Create a factory class that returns specific actions?
  • Declare all of the actions as private static final Action in some utility class?
  • Take advantage of a Java application framework?
  • Something else?
A: 

Action is a bad abstraction - an ActionListener welded to a poor man's Map.

Certainly do not assign them to a static as they are mutable and also need some context to operate usefully.

My general advice for GUI programming is to note that it is actually much the same as any other area of programming. Follow the usual good practices. Notably, layering, separation of concerns, use (implementation) inheritance rarely and don't write a big ball of mud.

Tom Hawtin - tackline
+1  A: 

You can group all your abstractAction using the dedicated Map javax.swing.actionmap . See http://java.sun.com/javase/6/docs/api/javax/swing/ActionMap.html

Moreover each JComponent has an internal actionMap (getActionMap()).

class MyComponent
extends JPanel
{
public static final String ACTION_NAME1="my.action.1";

public MyComponent()
 {
 AbstractAction action= new AbstractAction() { ... }
 getActionMap().put(ACTION_NAME1,action);
...

 menu.add(getActionMap().get(ACTION_NAME1));
 }

}

Hope it helps

Pierre
A: 

Also see this question, which is pretty much the same as what you're asking.

Dave Ray
A: 

Edit: I got the feeling people didn't believe this was possible or easy, so I did it--took about an hour from scratch--would have taken 40 mins if I'd just used a single method as a target instead of reflecting it out to separate methods for each menu item.

Here's the Tested source code. It works, but is one big method and ugly--refactor it if you use it. I may fix it up a little over the next few days, I've always wanted to have a copy of this to keep around to reuse.

--- original post

First of all, remember to separate your code from data. That means you should NEVER type:

new Menu("File...");

The string "File..." is data. If you start thinking this way, you will find that your question answers itself.

First you need to build up some data. You need to get "File..." and "Save" into menus. I generally start off with a string array (which you can easily move to a file)

new String[]{"File...","+Save","Load"...}

This is one of the simpler patterns I've started out with. Then you can parse out the + sign and use it to mean "Drop down a level in the menu when you add this one"

This is just a silly convention, invent your own if you don't like it.

The next step is binding that to code to run. You could have them all call the same method, but what a pain in the ass (Giant switch statement). One possibility is to use reflection to bind a method name while you are reading in the data. Here's one solution (again it might not fit your tastes)

new String[]{"File...[fileMenu]","+Save[saveMenu]","Load[loadMenu]"...}

Then you parse out the thing in square braces, reflectively hook it up to a method in your current class and you are set.

There is a temptation I ALWAYS have at this point, and I've learned to fight it because it NEVER works out. The temptation is to use the first set of data ("File...") and manipulate it to fit some pattern and auomatically bind to your code (in this case, remove all non-alpha chars, make the first letter lower case and append "Menu" to get the correct method name). Feel free to try this, it's very attractive and seems slick, but be ready to abandon it when it doesn't meet some need (such as two menu items with the exact same name in different sub-menus).

Another way would be if your language supports closures, then you could actually create the file name and closure in the same place..

Anyway, once you start coding like this, you'll find that ALL your menu construction is in a single 10 line method and you can alter it to suit your needs. I had a case where I had to change a set of menus to a button hierarchy and I did it in 2 minutes.

In the end, you can use this pattern to set up the action objects easily and change how they are used easily (in a single location, single line of code), so you experiment with them. There are many ways to use them, but if you don't do what I'm recommending here, you will end up having to re-implement across every menu item for every change, which is really annoying--after a single change you will have wasted more time than if you had just implemented a data-driven solution in the first place.

This really isn't hard code, should take like an hour or two then you never have to write new Menu("... again. Trust me, this kind of tooling is just about always worth it.

edit:

I just about always code data-driven these days. Usually I'll prototype a few things the normal way, recognize the pattern and refactor--and if you are refactoring correctly, the data just about always factors out and what you're left with is beautiful, tight and maintainable.

I could do what I suggested above in less than 1/2 an hour (maybe an hour to do the reflective version). This is almost always just as long as it would do to use the unfactored version, and from then on, your savings multiply for every change.

This is very similar to what people like about ruby, except with ruby they seem to insert even more data into their code (which makes it awfully hard to extract your data from the code completely, which is always a nice goal for internationalization).

Hmm, did I mention that if you're good at extracting your data like this, i18n is virtually free?

I suggest you just give it a try sometime and see what you think. Embedding the control in the strings is unnecessary if it makes you uncomfortable. I tend to use string/object arrays just because they are really easy to enter, are still in the file while you are coding and are trivial to externalize later, but if you like YML or XML or properties files, use whatever you're comfortable with--just abstract your data from your code!

Bill K
You really code your GUI's like this? :|
Richie_W
How can your GUI be fully factored without being data driven? There is so much repetitiveness.
Bill K
By the way, String.split (used to be StringTokenizer) can help a lot with parsing these items. A string like "File...|*|fileMethod" can be parsed in a second (the * means "top level" in this case--you can make up your "language" on the fly--very like a Ruby DSL but easier)
Bill K
A: 
  1. Create a base action for your application; this will help you IMMENSELY later on
  2. Do create actions as you have in your code, instead favor subclasses of your base action

To organize them, it will depend on what you are doing with them, and you may have some actions organized one way and others created a different way. It will all depend.

What you want is to have a consistent way to locate/create an action in your code.

Depending on your UI, you may need to differentiate between "static" actions (i.e. stuff that's always available in your app, such as the menu system) and dynamic actions that are created only on certain screens or in certain locations.

In any case, using concrete subclasses of your specialized base action will help you keep these things organized. What you don't want is to be specifying things like labels, mnemonics, and icons all over the place in your code.

davetron5000
+4  A: 

Applications that I have developed that need to use that same actions across menus, toolbars, and other buttons have been done using Swing Application Framework.

Swing Application Framework

This framework will allow you to have a resource file where you can define all menu text, tooltips, and ICONS. I think the icons are the key, you do not have to load them yourself. Also, if you have any actions that you need to enable/disable you can override the method to control its state.

The website is worth the read.

ShawnD