Chitika

Saturday, June 16, 2007

Sample Calculator GUI in Java Swing

Recently, I was giving my 1st-year IT students several Swing GUI exercises on OOP with Java lab sessions. One of the exercises was to create a simplified Calculator GUI that looks like the Calculator on standard Windows OS. The objective of the exercise was to make the students familiar with the basic Swing layout managers (note: I haven't thought them GridBag, Spring, or JGoodies Forms layouts), and the basic event-handling mechanism in Swing. The exercise was also meant for the students to learn more about the OOP concept in practice.




Here's the code that I gave the students at the end of the session:








public class Calculator {

  // constant values
  public static final int NONE = -1;
  public static final int ADD = 0;
  public static final int SUBTRACT = 1;
  public static final int MULTIPLY = 2;
  public static final int DIVIDE = 3;

  // values to be stored in the calculator memory
  private double leftValue = 0.0;
  private double rightValue = 0.0;
  private int lastOperation = NONE;
  private double multiplier = 1;
  private boolean DOT = false;

  // the user hits the + button
  public double add () {
    lastOperation = ADD;
    resetDOT();
    return leftValue;
  }

  // the user hits the - button
  public double subtract () {
    lastOperation = SUBTRACT;
    resetDOT();
    return leftValue;
  }

  // the user hits the * button
  public double multiply () {
    lastOperation = MULTIPLY;
    resetDOT();
    return leftValue;
  }

  // the user hits the / button
  public double divide () {
    lastOperation = DIVIDE;
    resetDOT();
    return leftValue;
  }

  // the user hits the = button
  public double equate () {
    switch (lastOperation) {
      case NONE: break;
      case ADD: leftValue = leftValue + rightValue; break;
      case SUBTRACT: leftValue = leftValue - rightValue; break;
      case MULTIPLY: leftValue = leftValue * rightValue; break;
      case DIVIDE: leftValue = leftValue / rightValue; break;
    }
    rightValue = 0;
    resetDOT();
    return leftValue;
  }

  // the user hits the 0-9 button
  public double number (int i) {
    double j = i * multiplier;
    if (DOT) {
      multiplier = multiplier / 10;
    }
    if (lastOperation == NONE) {
      leftValue = leftValue * (DOT ? 10(leftValue < (-j: j);
      return leftValue;
    else {
      rightValue = rightValue * (DOT ? 10(rightValue < (-j: j);
      return rightValue;
    }
  }

  // the user hits the +/- button
  public double plusMinus () {
    if (lastOperation == NONE) {
      leftValue = -leftValue;
      return leftValue;
    else {
      rightValue = -rightValue;
      return rightValue;
    }
  }

  // the user hits the C button
  public double reset () {
    lastOperation = NONE;
    leftValue = rightValue = 0;
    resetDOT();
    return leftValue;
  }

  // the user hits the sqrt button
  public double sqrt () {
    resetDOT();
    if (lastOperation == NONE) {
      leftValue = Math.sqrt (leftValue);
      return leftValue;
    else {
      rightValue = Math.sqrt (rightValue);
      return rightValue;
    }
  }

  // to reset the DOT & multiplier
  private void resetDOT() {
    DOT = false;
    multiplier = 1;
  }

  // the user hits the . button
  public double dot () {
    if (!DOT) {
      DOT = true;
      multiplier = 0.1;
    }
    return lastOperation == NONE ? leftValue : rightValue;
  }
}




I divided this small application into two different classes. The Calculator class above will act as the model for the application, and is responsible to maintain the state of the Calculator and to handle the user actions. Having a minimum amount of time duration for the lab session, many of the Calculator features were not implemented. Many exceptions were not caught nor handled properly, and there's a slightly different behavior with the sqrt button (when you compare it with the Windows OS version).

The whole point of this Calendar application was not to make a copycat of Windows OS version, rather it was to illustrate to the students what they can do with OOP and Swing.

Here's the second part of the application:








import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

public class CalculatorGUI implements ActionListener {

  private Calculator c = new Calculator ();

  private JFrame frame = new JFrame ("Calculator");
  private JPanel[] panels = new JPanel [6];
  private JTextField textField = new JTextField();
  private JButton resetButton = new JButton(" C ");
  private JButton[] numberButtons = new JButton[10];
  private JButton divideButton = new JButton ("/");
  private JButton multiplyButton = new JButton ("*");
  private JButton subtractButton = new JButton ("-");
  private JButton plusMinusButton = new JButton ("+/-");
  private JButton dotButton = new JButton (" . ");
  private JButton addButton = new JButton ("+");
  private JButton equateButton = new JButton (" = ");
  private JButton sqrtButton = new JButton ("sqrt");

  public void buildGUI () {

    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    JPanel contentPane = (JPanelframe.getContentPane();

    // initialize panels
    for (int i = 0; i < panels.length; i++) {
      panels[inew JPanel ();
    }

    // initialize button 0-9
    for (int i = 0; i < numberButtons.length; i++) {
      numberButtons[inew JButton (" " + i + " ");
      numberButtons[i].setActionCommand (String.valueOf(i));
      numberButtons[i].addActionListener (this);
    }

    // default layout = BorderLayout.CENTER
    textField.setColumns(20);
    textField.setText ("0");
    textField.setHorizontalAlignment (JTextField.RIGHT);
    panels[0].add (textField);

    // layout = FlowLayout.RIGHT
    panels[1].setLayout (new FlowLayout (FlowLayout.RIGHT));
    panels[1].add (resetButton);
    resetButton.setActionCommand ("RESET");
    resetButton.addActionListener (this);

    // layout = FlowLayout.LEFT
    panels[2].setLayout (new FlowLayout (FlowLayout.LEFT));
    panels[2].add (numberButtons[7]);
    panels[2].add (numberButtons[8]);
    panels[2].add (numberButtons[9]);
    panels[2].add (divideButton);
    panels[2].add (sqrtButton);
    divideButton.setActionCommand ("DIVIDE");
    divideButton.addActionListener (this);
    sqrtButton.setActionCommand ("SQRT");
    sqrtButton.addActionListener (this);

    // layout = FlowLayout.LEFT
    panels[3].setLayout (new FlowLayout (FlowLayout.LEFT));
    panels[3].add (numberButtons[4]);
    panels[3].add (numberButtons[5]);
    panels[3].add (numberButtons[6]);
    panels[3].add (multiplyButton);
    multiplyButton.setActionCommand ("MULTIPLY");
    multiplyButton.addActionListener (this);

    // layout = FlowLayout.LEFT
    panels[4].setLayout (new FlowLayout (FlowLayout.LEFT));
    panels[4].add (numberButtons[1]);
    panels[4].add (numberButtons[2]);
    panels[4].add (numberButtons[3]);
    panels[4].add (subtractButton);
    subtractButton.setActionCommand ("SUBTRACT");
    subtractButton.addActionListener (this);

    // layout = FlowLayout.LEFT
    panels[5].setLayout (new FlowLayout (FlowLayout.LEFT));
    panels[5].add (numberButtons[0]);
    panels[5].add (plusMinusButton);
    panels[5].add (dotButton);
    panels[5].add (addButton);
    panels[5].add (equateButton);
    plusMinusButton.setActionCommand ("PLUSMINUS");
    plusMinusButton.addActionListener (this);
    dotButton.setActionCommand ("DOT");
    dotButton.addActionListener (this);
    addButton.setActionCommand ("ADD");
    addButton.addActionListener (this);
    equateButton.setActionCommand ("EQUATE");
    equateButton.addActionListener (this);

    contentPane.setLayout (new BoxLayout (contentPane, BoxLayout.Y_AXIS));
    for (JPanel jPanel : panels) {
      contentPane.add (jPanel);
    }

    frame.pack ();
    frame.setVisible (true);
  }

  public void actionPerformed (ActionEvent e) {
    String actionCommand = e.getActionCommand();
    if (actionCommand == null || actionCommand.trim().length() <= 0) {
      return;
    }

    int number = -1;
    try {
      number = Integer.parseInt (actionCommand);
    catch (NumberFormatException e1) {
    }

    if (number >= 0) {
      // this is a number
      textField.setText ("" (c.number (number)));
    else {
      // this is not a number
      if (actionCommand.equals ("RESET")) {
        textField.setText ("" (c.reset()));
      else if (actionCommand.equals ("DIVIDE")) {
        textField.setText ("" (c.divide()));
      else if (actionCommand.equals ("SQRT")) {
        textField.setText ("" (c.sqrt()));
      else if (actionCommand.equals ("MULTIPLY")) {
        textField.setText ("" (c.multiply()));
      else if (actionCommand.equals ("SUBTRACT")) {
        textField.setText ("" (c.subtract()));
      else if (actionCommand.equals ("PLUSMINUS")) {
        textField.setText ("" (c.plusMinus()));
      else if (actionCommand.equals ("DOT")) {
        textField.setText ("" (c.dot()));
      else if (actionCommand.equals ("ADD")) {
        textField.setText ("" (c.add()));
      else if (actionCommand.equals ("EQUATE")) {
        textField.setText ("" (c.equate()));
      }
    }
  }

  public static void main(String[] args) {
    CalculatorGUI gui = new CalculatorGUI();
    gui.buildGUI ();
  }
}




I'm pretty sure that there are many solution variants out there. By focusing on the layout managers that are easy to use and understand, such as FlowLayout, BorderLayout and BoxLayout, I think this exercise has nicely served its main objective.

If you have any suggestions on how to help the students to understand the OOP + Swing concepts better, please do let me know.