Definition: Commands are executed when the user presses one of the buttons on the button panel. The buttons are for such functions as “Save”, “Cancel”, “Find”, etc. This action is executed on the panel which implements the Actionable interface. Not all buttons are present on every screen, but those which are present, must be able to notify the Actionable panel to perform the action. In the case of “Save”, for example, each of the input elements on the Actionable panel must save their input to the appropriate place. The “Cancel” button may do something like ask for confirmation, then close the Window, JFrame, or JDialog.
Issue: Since there are a large number of input screens, and a corresponding number of button panels, many with the same or similar buttons and commands to be executed, I needed a way to simplify this. I decided to use the Command Pattern to solve this problem.
Requirements: Here are the requirements to accomplish
this task:
- An interface defining the methods to be executed.
- Buttons, with locale text, to identify the actions required.
- A way to detect that a button has been pressed and to then have that button execute the appropriate method.
- The methods must have access to the items on the input panel to have the appropriate actions taken by invoking a method on them.
- A way to set up the button panel, with “missing” buttons, and with all buttons the same size.
Solution: My first solution was to create an enum
of the buttons/actions needed. I called this enum
“ButtonCommands” and it looked like this:
public enum ButtonCommands {
SAVE, EXIT, CANCEL, FIND;
JButton getButton(ActionListener al) {
JButton button = new JButton(...);
button.addActionListener(al);
button.setActionCommand(name());
...
return button;
}
void execute(String name, Actionable a) {
ButtonCommands command = ButtonCommands.valueOf(name);
switch(command) {
case SAVE: { a.save(); break; }
case EXIT: { a.exit(); break; }
case CANCEL: { a.cancel(); break; }
case FIND: { a.find(); break; }
default:
}
}
} // end enum ButtonCommands
There are far more commands than the four shown here,
but I just want to get the process across, and not go into every
detail. The
constructor for CommandButtons, which is not shown for simplicity,
retrieves the internationalized name and saves it, and also computes the
size of the largest (widest) button, then applies that size to all future
buttons. The getButton() method creates an actual button, with the
appropriate ActionListener, text, and the ActionCommand text. This
is the actual button which is placed on the button panel. The
ActionListener is usually the button panel. Here is a short look at
the button panel:
public class ButtonPanel implements ActionListener {
Actionable a;
public ButtonPanel(Actionable a, ButtonCommands[] b) {
this.a = a;
// the buttons are laid out on the panel
}
public void actionPerformed(ActionEvent ae) {
String cmd = ae.getActionCommand();
if (ButtonCommands.isCommand(cmd) && a != null) {
ButtonCommands.execute(cmd, a);
}
}
} // end class ButtonPanel
The key here is that the ButtonPanel is the listener for
the buttons it contains, then calls the execute() method for the appropriate
command based on the ActionCommand name, and the Actionable object provided
when the ButtonPanel is created. The ButtonCommands.isCommand(cmd)
method, which was not shown or described above, just looks up the ActionCommand
among the values() of ButtonCommands to verify it is a valid ButtonCommands
name. Here is how the input panel looks, in short:
public class InputPanel extends ActionablePanel {
ButtonCommands[] buttons = { ButtonCommands.SAVE, ButtonCommands.EXIT};
ButtonPanel bp = new ButtonPanel(this, buttons);
JTextField input = new JTextField("data here");
...
@Override public void save() {
// save the input field data
}
@Override public void exit() {
// exit
}
} // end class InputPanel
The InputPanel extends the ActionablePanel which is
a plain JPanel which implements Actionable by using a “stub”
method for each of the
commands. The “stub” method does nothing, so only the
needed commands, in this case SAVE and EXIT, need to be overridden on
this InputPanel to do anything.
So here is what happens when the user presses (clicks on) the
“Save” button.
- The “Save” button fires a ActionEvent to its ActionListener, the ButtonPanel.
- The ButtonPanel method actionPerformed() is called.
- The ActionCommand string is retrieved from the pressed button. In this case it is “SAVE”.
- ButtonCommand.isCommand() is called to verify it is a ButtonCommand.
- Because it is, ButtonCommand.execute() is called with the ActionCommand and Actionable object.
- The ActionCommand is converted to a ButtonCommand object using valueOf().
- The switch calls the save() method on the Actionable object.
- The save() method then performs the appropriate steps.
I found one thing that bothers me about this method. That is, it relies on the ActionCommand to execute the correct command. I decided to try a different approach, which you can see here.