Home CPSC 220

More GUI Programming

More Widgets

There are many more types of "widgets" that can be used in GUI programs. For the most part, they work similarly to the ones we have seen - they can be created and added to windows in the same way as buttons or labels. They each have different constructors and methods specific to the type of widget it is:

There are also a number of "dialogs" which you can present the user where they supply some information:


Nesting Layouts

There are more types of layouts beyond the "BoxLayout" that we saw previously. This page lists them along with visual examples of how they work.

If necessary, programs can use multiple layouts at one time. To do this, you need to create a JPanel object. A JPanel is a place where multiple widgets can be placed together, using some layout.

For example, in the Calculator example below, the overall window uses a vertical BoxLayout. This layout contains three things: a JLabel for the display, a JPanel for most of the buttons, and finally another JPanel for two more buttons. The middle panel uses a GridLayout while the bottom panel uses horizontal BoxLayout.

Using this approach, you can arrange widgets on the screen in whichever way you want.


Calculator Example

The slightly longer example below is a simple GUI calculator program written in Java.

Some notes on the program:


// CalculatorGui.java

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

// represents one of our calculator buttons
// the order is how they will be added to the grid
enum ButtonType {
    // row 1
    N1,
    N2,
    N3,
    PLUS,
    
    // row 2
    N4,
    N5,
    N6,
    MINUS,
    
    // row 3
    N7,
    N8,
    N9,
    TIMES,
    
    // row 4
    N0,
    DOT,
    SQRT,
    DIVIDE,
    
    // bottom pane
    EQUALS,
    CLEAR
}

// the calculator can be in any of these states
enum CalculatorState {
    DEFAULT,     // reading in numbers but not yet at an operator
    ADDING,      // we entered one number and pushed the + button, now reading the second
    SUBTRACTING, // ditto but for -
    MULTIPLYING, // ditto but for *
    DIVIDING,    // ditto but for /
    DONE         // we have finished a calculation and are displaying the result
                 // we can use this for future results, but a number will overwrite
}

// the ButtonListener handles each button press
class ButtonListener implements ActionListener {
    // each button listener stores which button it is listening to
    // it also stores the calculator object so it can tell it when
    // the buttons are pressed
    private ButtonType button;
    private Calculator calc;

    // given the button we are listening for, and the calculator object
    public ButtonListener(ButtonType b, Calculator c) {
        button = b;
        calc = c;
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        // check which button we are listening for
        switch (button) {
            case N0:
            case N1:
            case N2:
            case N3:
            case N4:
            case N5:
            case N6:
            case N7:
            case N8:
            case N9:
                // for any of the digits, find out what digit it is
                // then add it to the calculator
                String stringDigit = button.name().substring(1);
                int digit = Integer.parseInt(stringDigit);
                calc.addDigit(digit);
                break;
                
            // for the other buttons, call the appropriate calculator method
            case DOT:
                calc.setDot();
                break;
            case PLUS:
                calc.setOperator('+');
                break;
            case MINUS:
                calc.setOperator('-');
                break;
            case TIMES:
                calc.setOperator('*');
                break;
            case DIVIDE:
                calc.setOperator('/');
                break;
            case SQRT:
                calc.sqrt();
                break;
            case EQUALS:
                calc.equals();
                break;
            case CLEAR:
                calc.clear();
                break;
        }
    }
}

// the calculator takes care of the actual calcualtions, and stores the
// current number this class extends the JLabel so it can also serve as
// a display of the current number
class Calculator extends JLabel {
    // the state which we are in
    CalculatorState state;
    
    // the current value we are using
    private double value;
    
    // the saved value if we are doing an operator
    private double saved;
    
    // whether or not new digits are added after the dot
    private boolean dot;
    
    // the amount of places after the dot to add
    private int place;

    public Calculator() {
        // clear should initialize us to 0, so just call that
        clear();
    }

    // called to clear out the calculator
    public void clear() {
        // set all the default values
        value = 0.0;
        saved = 0.0;
        dot = false;
        place = 0;
        state = CalculatorState.DEFAULT;
        
        // update the display
        update();
    }
    
    // update the display with the current value
    public void update() {
        // convert the value to a string
        String stringValue = Double.toString(value);
        
        // since we are a JLabel, we have a setText function, just call it
        setText(stringValue);
    }
    
    // called when the user pushes one of the digit buttons
    public void addDigit(int digit) {
        // if the state is done, reset things first
        if (state == CalculatorState.DONE) {
            clear();
        }
        
        // if there has not been a dot yet
        if (!dot) {
            // add this digit to the end, but multiplying the current value
            // by ten and then adding it in
            value = value * 10 + digit;
        } else {
            // add this digit in the next decimal place available
            value = value + digit / (Math.pow(10.0, place));
            place++;
        }
        
        // update the display
        update();
    }
    
    // called when equals gets pushed
    public void equals() {
        // apply the operator depending on our state
        switch (state) {
            case DEFAULT:
                // do nothing!
                break;
            case ADDING:
                value = saved + value;
                break;
            case SUBTRACTING:
                value = saved - value;
                break;
            case MULTIPLYING:
                value = saved * value;
                break;
            case DIVIDING:
                value = saved / value;
                break;
        }
        // update to the new value
        update();
        
        // go to the DONE state
        state = CalculatorState.DONE;
    }
    
    // called when the dot button is pushed
    public void setDot() {
        // if we already hada a dot, this is an error!
        if (dot) {
            throw new IllegalStateException("Can't have two decimal points");
        }
        
        // set the dot
        dot = true;
        place = 1;
    }
    
    // called when an operator button is pushed
    public void setOperator(char operator) {
        // save the current value;
        saved = value;
        
        // reset value to read
        value = 0.0;
        dot = false;
        place = 0;
        
        // remember the operator so we can apply it later
        switch (operator) {
            case '+':
                state = CalculatorState.ADDING;
                break;
            case '-':
                state = CalculatorState.SUBTRACTING;
                break;
            case '*':
                state = CalculatorState.MULTIPLYING;
                break;
            case '/':
                state = CalculatorState.DIVIDING;
                break;
            default:
                throw new IllegalArgumentException("Invalid Operator");
        }
    }
    
    // called for the square root operation
    public void sqrt() {
        value = Math.sqrt(value);
        update();
    }
}

public class CalculatorGui {

    // just returns the name which should be used for a button
    public static String buttonName(ButtonType b) {
        switch (b) {
            // all the numbered ones just return the number part
            case N0:
            case N1:
            case N2:
            case N3:
            case N4:
            case N5:
            case N6:
            case N7:
            case N8:
            case N9:
                // return the name's substring starting at 1
                return b.name().substring(1);
                
            // the rest
            case DOT:
                return ".";
            case PLUS:
                return "+";
            case MINUS:
                return "-";
            case TIMES:
                return "×";
            case DIVIDE:
                return "÷";
            case SQRT:
                return "√";
            case EQUALS:
                return "=";
            case CLEAR:
                return "C";
        }
        
        throw new IllegalArgumentException("Invlaid button type " + b.name());
    }

    public static void main(String args[]) {
        // create the calculator object which handles calculations
        Calculator calc = new Calculator();
        
        // create and set up the window.
        JFrame frame = new JFrame("Calculator");

        // make the program close when the window closes
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // we need a "panel" for the grid buttons
        JPanel grid = new JPanel();

        // create a grid layout for the 16 buttons other than clear and equals
        GridLayout layout = new GridLayout(4, 4);
        grid.setLayout(layout);

        // add those first 16 buttons to it
        for (int i = 0; i < 16; i++) {
            // find this button type
            ButtonType b = ButtonType.values()[i];

            // create a GUI button for it with the right name
            String name = buttonName(b);
            JButton button = new JButton(name);

            // create a listener for it
            ButtonListener listener = new ButtonListener(b, calc);
            button.addActionListener(listener);

            // add it to the grid
            grid.add(button);
        }

        // now we create a vertical box layout for the textbox, grid of buttons
        // and the row of buttons at the bottom
        BoxLayout mainLayout = new BoxLayout(frame.getContentPane(), BoxLayout.Y_AXIS);
        frame.getContentPane().setLayout(mainLayout);

        // add the calculator display line to the layout
        frame.getContentPane().add(calc);

        // add the grid
        frame.getContentPane().add(grid);

        // now create another panel and layout for displaying the two buttons at bottom
        JPanel bottom = new JPanel();
        BoxLayout bottomLayout = new BoxLayout(bottom, BoxLayout.X_AXIS);

        // create the bottom two buttons and listeners for them
        JButton clear = new JButton(buttonName(ButtonType.CLEAR));
        ButtonListener clearListener = new ButtonListener(ButtonType.CLEAR, calc);
        clear.addActionListener(clearListener);
        JButton equals = new JButton(buttonName(ButtonType.EQUALS));
        ButtonListener equalsListener = new ButtonListener(ButtonType.EQUALS, calc);
        equals.addActionListener(equalsListener);

        // add them to the bottom panel
        bottom.add(clear);
        bottom.add(equals);

        // add the bottom row to the window
        frame.getContentPane().add(bottom);

        // display the window.
        frame.pack();
        frame.setVisible(true);
    }
}

Copyright © 2018 Ian Finlayson | Licensed under a Creative Commons Attribution 4.0 International License.