by Kam-Hung Soh (kamhung dot soh at gmail dot com) 2009/05/24 03:59:47
This article describes implementing a four-function calculator application in Java 1.6 and Swing, which is a common exercise to write a application slightly more complex than Hello World.
The calculator is window with a menu bar, single-line text display and buttons. The user can only enter data using the buttons in the display and keyboard input is disabled. The text display and buttons are laid out in a tabular format. The menu bar has File / Exit and Help / About menu items.
To facilitate discussion, below is the folder structure of the source code:
./src: infixcalculator resources ./src/infixcalculator: AboutAction.java HelpAction.java InfixCalculatorResource.java ExitAction.java images Main.java FileAction.java InfixCalculator.java Model.java ./src/infixcalculator/images: InfixCalculator_32x32.gif InfixCalculator_32x32.xcf ./src/resources: InfixCalculator.properties
This application roughly follows the model-view-controller (MVC) pattern. The model (current result, argument and operator) is stored in the Model class, while the view and controller are implemented in the InfixCalculator class.
The Model is very simple and there is little to say about it. Most of the complexity in this exercise is in the controller which processes the user's input (the actionPerformed() method). The text display (view) is updated in doEdit() when the user enters a number, while the model is updated in doEvaluate() when the user enters an operator (see code below from InfixCalculator.java).
126 public void actionPerformed(ActionEvent e) {
127 String input = e.getActionCommand();
128 String buffer = jtf.getText();
129
130 if (input.matches(RE_EDIT)) {
131 buffer = doEdit(buffer, input);
132 } else if (input.matches(RE_EVALUATE)) {
133 buffer = doEvaluate(buffer, input);
134 }
135 previousInput = input;
136 jtf.setText(buffer);
137 }
There is some coupling between doEdit() and doEvaluate to make the application work like a real four-function calculator:
The menus and menu items are defined by extending the AbstractAction class. You can encapsulate standard items such as the name, description and shortcut key in an Action. The menu bar displays these actions with some text, a tooltip and underlines the shortcut key. If you also define a toolbar with icons and the same actions, the toolbar should display an icon and a tooltip.
For instance, FileAction.java below defines the File menu. Note that the definition of various fields are obtained from a resource bundle (lines 11 to 14).
1 package infixcalculator;
2
3 import java.awt.event.ActionEvent;
4 import javax.swing.AbstractAction;
5 import javax.swing.Action;
6 import java.util.ResourceBundle;
7
8 class FileAction extends AbstractAction {
9 public FileAction() {
10 super();
11 ResourceBundle res = InfixCalculatorResource.getInstance();
12 putValue(Action.NAME, res.getString("FileAction.Name"));
13 putValue(Action.SHORT_DESCRIPTION, res.getString("FileAction.Description"));
14 putValue(Action.MNEMONIC_KEY, new Integer(res.getString("FileAction.Mnemonic")));
15 }
16 public void actionPerformed(ActionEvent e) {
17 }
18 }
Below are the resource strings used by FileAction in InfixCalculator.properties.
3 FileAction.Name = File
4 FileAction.Description = File
5 # java.awt.event.KeyEvent.VK_F
6 FileAction.Mnemonic = 70
Here's how the FileAction is added to the menu bar in InfixCalculator.java:
72 JMenuBar menuBar = new JMenuBar();
73 getRootPane().setJMenuBar(menuBar);
74
75 JMenu menu = new JMenu(new FileAction());
76 menuBar.add(menu);
The layout of the single-line text display and buttons are controlled by a GridBagLayout (essentially a table). The text display occupies the entire top row and the buttons occupy each cell in a 4 by 5 grid below the text display. Below is a rough ASCII sketch of the cells in a table (T = text display, D = delete, C = clear entry, A = clear all, N = negate):
0 1 2 3 4 0 T T T T T 1 7 8 9 + D 2 4 5 6 - C 3 1 2 3 * A 4 0 . N / =
For simplicity, each button occupies only one cell and you only need a name to define a button. In lines 106 to 122 of InfixCalculator.java, we define an array of button names and use a loop to create each button and set its gridx and gridy position in the table. Like the menus and menu items, the values are read from a resource bundle (lines 116 to 120 in InfixCalculator.java).
106 String[] buttons = {
107 "Button7", "Button8", "Button9", "ButtonPlus", "ButtonBackspace",
108 "Button4", "Button5", "Button6", "ButtonMinus", "ButtonClearEntry",
109 "Button1", "Button2", "Button3", "ButtonMultiply", "ButtonClearAll",
110 "Button0", "ButtonPoint", "ButtonNegate", "ButtonDivide", "ButtonEvaluate"
111 };
112 for (int i = 0; i < buttons.length; ++i) {
113 String name = buttons[i];
114 JButton button = new JButton();
115 button.setName(name);
116 button.setText(res.getString(name + ".Text"));
117 button.setToolTipText(res.getString(name + ".ToolTipText"));
118 button.addActionListener(this);
119 gbc.gridx = Integer.parseInt(res.getString(name + ".gridx"));
120 gbc.gridy = Integer.parseInt(res.getString(name + ".gridy"));
121 gridbag.setConstraints(button, gbc);
122 cp.add(button);
Here's an example of the resources for one button in InfixCalculator.properties:
26 Button7.Text = 7
27 Button7.ToolTipText = 7
28 Button7.gridx = 0
29 Button7.gridy = 1
This application uses a run-time resource bundle stored in resources/InfixCalculator.properties file to define many properties of the application, such as the window title, menu item names and button positions. The resource bundle is initialized in InfixCalculatorResource.java. Usually, resource bundles are described in the context of localization (or l10n) but it is straightforward (if a little tedious) to define other simple properties.
A .properties file is read when the application is started, so you can change the appearance of the application by editing the .properties file in the JAR archive. For instance, you can arrange the buttons in the same order as a phone keypad (123 in the top row and 789 in the bottom row) by changing the appropriate gridy values. Of course, customization is limited by the functionality that is implemented.
A simple four-function calculator was implemented in Java and Swing. A Java resource bundle was used to simplify building the layout of the calculator's buttons and provide simple customization feature for the user. Swing's AbstractAction was used to encapsulate properties of actions in the menu bar.
Some improvements that can be made in future projects:
JTextField control.doEdit() and doEvaluate() functions may have been avoided by better analysis at the start.