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: I first had to create a JButton which
holds an CommandButtonsEnum instance. This was straight forward,
and it has a constructor which takes both the CommandButtonsEnum
instance and the display text as arguments. That’s about
the only difference from a JButton. It’s called
(unsurprisingly) a “CommandButton”.
My next solution was to create an enum
of the buttons/actions needed. I called this enum
“CommandButtonsEnum” and it looked like this:
public enum CommandButtonsEnum {
SAVE { public void execute(Actionable a) { a.save(); } },
EXIT { public void execute(Actionable a) { a.exit(); } },
CANCEL { public void execute(Actionable a) { a.cancel(); } },
FIND { public void execute(Actionable a) { a.find(); } };
CommandButtonJButton getButton(ActionListener al) {
CommandButton button = new CommandButton(this, ...);
button.addActionListener(al);
...
return button;
}
} // end enum CommandButtonsEnum
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 CommandButtonsEnum, 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 size. 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 ButtonPanel2 implements ActionListener {
Actionable a;
public ButtonPanel2(Actionable a, CommandButtonsEnum[] b) {
this.a = a;
// the buttons are laid out on the panel
}
public void actionPerformed(ActionEvent ae) {
Object source = ae.getSource();
// CHANGES HERE
if (source instanceOf CommandButton) {
((CommandButton)source).execute(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 CommandButton, and the Actionable object provided
when the ButtonPanel is created. The source is checked to verify
it is a valid CommandButton. Here is how the input panel looks,
in short:
public class InputPanel extends ActionablePanel {
CommandButtonsEnum[] buttons = { CommandButtonsEnum.SAVE, CommandButtonsEnum.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 an ActionEvent to its ActionListener, the ButtonPanel.
- The ButtonPanel method actionPerformed() is called.
- The source of the ActionEvent is checked to be sure it is a CommandButton. In this case it is.
- CommandButton.execute() is called
- Which calls the execute() method on the enclosed CommandButtonEnum.SAVE instance.
- This execute() method calls the save() method on the Actionable object.
- The save() method then performs the appropriate steps.
This is slightly better than the original version, but I'm not so sure it is really worth the effort to change all of the existing code which uses these classes. At the moment I’ll leave everything as it is.