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
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);
From my experience, the 'most' standard way of handling actions performed on a Swing GUI is to create ActionListener
s and have them handle ActionEvent
s 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.
I usually take the following approach:
Action
with the containing Component
's action map.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.).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 Action
s to ActionListener
s for exposing Action
s 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With