views:

261

answers:

3

Typically when I'm creating a Swing (or any UI) application, I have various Actions that appear on menu items and buttons. I usually create an action registry and store the actions in there and then when certain things occur, I disable/enable actions in the registry based on the state of the application. I wouldn't call myself an avid Swing developer, although I know my way around it well enough, but is this a pretty typical pattern for managing Actions? Or is there a more standard way of doing it?

thanks,

Jeff

+4  A: 

From my experience, the 'most' standard way of handling actions performed on a Swing GUI is to create ActionListeners and have them handle ActionEvents directly for the components with which they are registered. It is a simple design and it follows convention with other sorts of GUI events in the Swing framework (MouseListener/MouseEvent, TableModelListener/TableModelEvent, etc).

The Action framework you describe is a powerful tool to allow for sharing of actions between many input methods (ie, having a toolbar button and menu item perform the same action, and therefore sharing the same Object for handling the events triggered by both, etc.). This abstraction is pretty cool, but Sun cautions that it is a bit heavier-weighted than the simple Observers. From the Action JavaDoc:

Note that Action implementations tend to be more expensive in terms of storage than a typical ActionListener, which does not offer the benefits of centralized control of functionality and broadcast of property changes. For this reason, you should take care to only use Actions where their benefits are desired, and use simple ActionListeners elsewhere.

akf
That all makes sense that's generally the approach I take. I guess I'm just trying to find the best away to manage the Actions when I do need to use them...
Jeff Storey
+2  A: 

I usually take the following approach:

  • Register the Action with the containing Component's action map.
  • Define a public String constant allowing the application bootstrap code to "pull out" the Action from the Component in required (e.g. to add it to a JToolBar, JMenuBar, etc.).
  • Define a private updateActionStates() method within the Component, which is called when the user performs some action (e.g. selects N rows from a JTable). This method enables / disables all bespoke actions based on the current state of the Component.

Example:

public class MyPanel extends JPanel {
  public static final String MY_ACTION_NAME = "MyAction";

  private final JTable myTable;       

  public MyPanel() {
    // Create action and define behaviour.
    this.myAction = new AbstractAction(MY_ACTION_NAME, ...);

    // Register with component's action map.
    getActionMap().put(myAction.getValue(Action.NAME), myAction);

    // Optionally register keyboard shortcuts using component's input map.
    getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(...);

    // Create JTable and add a call to updateActionStates when the selection changes.
    myTable = new JTable(...);
    myTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
      public void valueChanged(ListSelectionEvent evt) {
        updateActionStates();
      }
    });
  }

  private void updateActionStates() {
    // Action will only be enabled if one table row is selected.
    getActionMap.get(MY_ACTION_NAME).setEnabled(myTable.getSelectedRowCount == 1);
  }
}

// Application start-up code:

MyPanel pnl = new MyPanel();
JToolBar toolBar = new JToolBar();
// Pull out action from action map and add to toolbar.
toolBar.add(pnl.getActionMap().get(MyPanel.MY_ACTION_NAME));

Incidentally, I typically prefer Actions to ActionListeners for exposing Actions that form part of my Component's API. For Actions that merely exist within the Component (e.g. a dialog's "Clear" button) I typically use ActionListener. However, I disagree with akf about ActionListener being the most standard approach - This may be true of smaller GUIs but not more complex Swing applications.

Adamski
+4  A: 

Jeff, your approach seems like a good approach. I do the same thing. I call the registry ActionHandler and it looks like this:

import com.google.common.collect.ClassToInstanceMap;
import com.google.common.collect.ImmutableClassToInstanceMap;

import javax.swing.*;
import javax.swing.text.DefaultEditorKit;

public class ActionHandler {

    private static final ClassToInstanceMap<Action> actionMap =
            new ImmutableClassToInstanceMap.Builder<Action>().
                    put(DefaultEditorKit.CutAction.class, new DefaultEditorKit.CutAction()).
                    put(DefaultEditorKit.CopyAction.class, new DefaultEditorKit.CopyAction()).
                    put(DefaultEditorKit.PasteAction.class, new DefaultEditorKit.PasteAction()).
                    put(RefreshAction.class, new RefreshAction()).
                    put(MinimizeAction.class, new MinimizeAction()).
                    put(ZoomAction.class, new ZoomAction()).
                    build();

    public static Action getActionFor(Class<? extends Action> actionClasss) {
        return actionMap.getInstance(actionClasss);
    }
}

Now to disable, say ZoomAction, I use

   ActionHandler.getActionFor(ZoomAction.class).setEnabled(false);
Steve McLeod