Using graphical user interfaces (GUIs)

Here we will make a brief introduction into using graphical interfaces, to be able to work with problems that require visual output.

Java libraries related to graphical user interfaces are collected under java.awt and java.swing libraries, which came out at different periods during the language's development. One of the classes, JFrame represent an application window in the system. All other visual components are placed within that application window.

Simple GUIs

A typical GUI application is written by extending JFrame class:

    import javax.swing.*;        

    class SimpleGUI extends JFrame{
        SimpleGUI(String title) {
            super(title);
            setDefaultCloseOperation(EXIT_ON_CLOSE);
        }

        public static void main(String[] args) {
            //create the window
            SimpleGUI mainFrame = new SimpleGUI("Simple application");
            //Display the window.
            mainFrame.setVisible(true);
        }
    }

The only parameter when constructing JFrame objects is the title of the window. In reality you need to make at least one more modification to this class to enable the user close the application, the application will work without it (displaying a very minimal window) but cannot be closed except from the command line.

Adding visual components

In most cases you'd like to add some visual elements to a window. elements like JLabel and JButton from swing library are simplest examples:

    import javax.swing.*;

    class SimpleGUI extends JFrame{
        SimpleGUI(String title) {
            super(title);
            setDefaultCloseOperation(EXIT_ON_CLOSE);
            //add some visual elements
            setLayout(new FlowLayout());
            JLabel label = new JLabel("This application does nothing useful yet");
            add(label);
            JButton closeButton=new JButton("Close");
            add(closeButton);

            //pack the frame, ready to display
            pack();
        }
        public static void main(String[] args) {
            //create the window
            SimpleGUI mainFrame = new SimpleGUI("Simple application");
            //Display the window.
            mainFrame.setVisible(true);
        }
    }

Note how the program window does not repond to clicking the close button.

The FlowLayout is a simple layout for stacking added visual elements. Other layouts exist for more controlled placement of visual elements.

Adding event handling

Making the program respond to user input such as clicking buttons, require us to write event handler classes. This is done by extending ActionListener class from java.awt.event library:

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

    class MyEventListenerForCloseButton implements ActionListener {
        MyEventListenerForCloseButton() {super();}
        public void actionPerformed(ActionEvent e){
            System.exit(0);
        }
    }

    class MyEventListenerForFrameMouseActions implements MouseListener {
        JLabel reportTo;
        MyEventListenerForFrameMouseActions(JLabel reportTo) {
            super();
            this.reportTo=reportTo;
        }
        public void mouseClicked(MouseEvent e) {report(e);}
        public void mouseEntered(MouseEvent e) {report(e);}
        public void mouseExited(MouseEvent e) {report(e);}
        public void mousePressed(MouseEvent e) {report(e);}
        public void mouseReleased(MouseEvent e) {report(e);}
        void report(MouseEvent e) {reportTo.setText(e.toString());}
    }

    class SimpleGUI extends JFrame{
        SimpleGUI(String title) {
            super(title);
            setDefaultCloseOperation(EXIT_ON_CLOSE);
            setPreferredSize(new Dimension(600,100)); 
            //add some visual elements
            setLayout(new FlowLayout());
            JLabel label = new JLabel("This application does nothing useful");
            add(label);
            JButton closeButton=new JButton("Close");
            add(closeButton);

            //add action listeners
            addMouseListener(new MyEventListenerForFrameMouseActions(label));
            closeButton.addActionListener(new MyEventListenerForCloseButton());

            //pack the frame, ready to display
            pack();
        }
        public static void main(String[] args) {
            //create the window
            SimpleGUI mainFrame = new SimpleGUI("Simple application");
            //Display the window.
            mainFrame.setVisible(true);
        }
    }

Anonymous classes

Java provides a special syntax for using anonymous classes. An anonymous classes is appropriate when only one instance of a class is needed. The syntax is as follows:

    interface I {
        void f();
    }
    class MyProgram {
        public static void main(String[] args) {
            I i=new I() {
                void f() {System.out.println("implemented");}
               }
            i.f();
        }
    }

compiling the above program will result in three .class files: I.class, MyProgram.class, and MyProgram$1.class. The last file is the compiled version of the anonymous class which is declared and instantiated together.

Using anonymous classes for GUI event handlers

Event handler are needef often in a GUI application. Declaring separate classes is possible, but makes the code unreadable since responses to events are declared in different places than visual components. Thus most Java developers prefer anonymnous classes for event handling:

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


    class SimpleGUI extends JFrame{
        SimpleGUI(String title) {
            super(title);
            setDefaultCloseOperation(EXIT_ON_CLOSE);
            setPreferredSize(new Dimension(600,100)); 
            //add some visual elements
            setLayout(new FlowLayout());
            JLabel label = new JLabel("This application does nothing useful");
            add(label);
            JButton closeButton=new JButton("Close");
            add(closeButton);

            //add action listeners
            closeButton.addActionListener(new ActionListener(){
                public void actionPerformed(ActionEvent e){
                    System.exit(0);
                }
            });

            //pack the frame, ready to display
            pack();
        }
        public static void main(String[] args) {
            //create the window
            SimpleGUI mainFrame = new SimpleGUI("Simple application");
            //Display the window.
            mainFrame.setVisible(true);
        }
    }

GUI: Drawing

The swing library provides a generic component called JPanel which can be used for drawing upon. The following program uses a class which extends JPanel, and adds an instance to the main window. Note that the extended class overrides paintComponent method of the JPanel to do the actual drawing. A large variety of shapes can be drawn this way:

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

    class DrawingPanel extends JPanel {
        DrawingPanel() {
            super();
            setPreferredSize(new Dimension(400,400));
        }

        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            ((Graphics2D)g).draw(new Ellipse2D.Double(100,100,50,70));
        }
    }

    class DrawingGUI extends JFrame{
        DrawingPanel panel;
        DrawingGUI(String title) {
            super(title);
            setDefaultCloseOperation(EXIT_ON_CLOSE);
            setLayout(new FlowLayout());
            setPreferredSize(new Dimension(400,500));        

            panel = new DrawingPanel();
            add(panel);
            JButton closeButton=new JButton("Close");
            add(closeButton);

            closeButton.addActionListener(new ActionListener(){
                public void actionPerformed(ActionEvent e){
                    System.exit(0);
                }
            });

            //pack the frame, ready to display
            pack();
        }
        public static void main(String[] args) {
            DrawingGUI mainFrame = new DrawingGUI("Drawing");
            mainFrame.setVisible(true);
            mainFrame.repaint();
        }
    }

Case study: A Sketchpad

The following application allow drawing on screen by dragging mouse:

    /**
     * This application follows mouse drags and sketches its trace.
     * Author: Mehmet Gencer, mgencer@bilgi.edu.tr
     *
     * To record the line segments that make up the sketch, a linked list is used. 
    */
    import java.awt.*;
    import java.util.*;
    import java.awt.event.*;
    import javax.swing.*;

    class LineSegment {
        int x1,y1,x2,y2;
        LineSegment (int x1, int y1, int x2, int y2) {
            this.x1=x1;
            this.y1=y1;
            this.x2=x2;
            this.y2=y2;
        }
    }

    class SketchPanel extends JPanel {
        ArrayList<LineSegment> lines;
        int lastX,lastY;
        SketchPanel() {
            super();
            this.lines=new ArrayList<LineSegment>();
            setPreferredSize(new Dimension(600,600));
            addMouseListener(new MouseListener() {
                public void mouseEntered(MouseEvent event) {}
                public void mouseExited(MouseEvent event) {}
                public void mousePressed(MouseEvent event) {
                    lastX=event.getX();
                    lastY=event.getY();
                }
                public void mouseReleased(MouseEvent event) {}
                public void mouseClicked(MouseEvent event) {}
            });
            addMouseMotionListener(new MouseMotionListener() {
                public void mouseDragged(MouseEvent event) {
                    int currentX=event.getX();
                    int currentY=event.getY();
                    LineSegment line=new LineSegment(lastX,lastY,currentX,currentY);
                    lines.add(line);
                    lastX=currentX;
                    lastY=currentY;
                    getGraphics().drawLine(line.x1,line.y1,line.x2,line.y2);
                }
                public void mouseMoved(MouseEvent event) {}        
            });
        }
        public void paintComponent(Graphics g) {
            for(LineSegment line:lines)
                g.drawLine(line.x1,line.y1,line.x2,line.y2);
        }
    }

    class Sketchpad extends JFrame{
        SketchPanel panel;
        Sketchpad (String title) {
            super(title);
            setDefaultCloseOperation(EXIT_ON_CLOSE);
            setPreferredSize(new Dimension(650,700)); 
            setLayout(new FlowLayout());
            panel=new SketchPanel();
            add(panel);
            JButton closeButton=new JButton("Close");
            add(closeButton);

            //add action listeners
            closeButton.addActionListener(new ActionListener(){
                public void actionPerformed(ActionEvent e){
                    System.exit(0);
                }
            });

            //pack the frame, ready to display
            pack();
        }
        public static void main(String[] args) {
            Sketchpad mainFrame = new Sketchpad("Sketchpad");
            mainFrame.setVisible(true);
        }
    } 

Case study: Quadratic Julia sets

A quadratic Julia set is a set of points on the Complex numbers place for which when the function \(f(z)=z^2+c\) is iteratively applied, the value does not diverge.

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

    class Complex {
        double real,imag;
        Complex(double real, double imag) {
            this.real=real;
            this.imag=imag;
        }
        Complex add(Complex with) {
            return new Complex(real+with.real,imag+with.imag);
        }
        Complex multiply(Complex with) {
            return new Complex(real*with.real-imag*with.imag,real*with.imag+imag*with.real);
        }
        double size() {
            return Math.sqrt(real*real+imag*imag);
        }
        public String toString() {
            return String.format("%.2f+i%.2f",real,imag);
        }
    }

    class JuliaSet {
        Complex rangeStart=new Complex(-1,-1);
        Complex rangeEnd=new Complex(1,1);
        int resolution=400;
        int[][] results;
        Complex c;
        JuliaSet(int resolution) {
            this(resolution,-0.8,0.156);
        }
        JuliaSet(int resolution, double creal, double cimag) {
            this.resolution=resolution;
            results=new int[resolution][resolution];
            c=new Complex(creal,cimag);
        }
        int f(Complex z){
            int i=0;
            double limit=100;
            Complex fz=z;
            do {
                fz=fz.multiply(fz).add(c);
                i++;
            } while(i<255 && fz.size()<limit);
            return i;
        }

        public void run() {
            System.out.println("running");
            for(int i=0;i<resolution;i++)
                for(int j=0;j<resolution;j++)
                    results[i][j]=f(new Complex(((i+0.0)/resolution)*(rangeEnd.real-rangeStart.real)+rangeStart.real, ((j+0.0)/resolution)*(rangeEnd.imag-rangeStart.imag)+rangeStart.imag));
            System.out.println("finished");
        }
    }
    class JuliaDrawPanel extends JPanel {
        JuliaSet set;
        JuliaDrawPanel(JuliaSet set) {
            super();
            setPreferredSize(new Dimension(set.resolution,set.resolution));
            this.set=set;
        }
        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            for(int i=0;i<set.resolution;i++)
                for(int j=0;j<set.resolution;j++) {
                    g.setColor(new Color(set.results[i][j],set.results[i][j],set.results[i][j]));
                    g.fillRect(i,j,i+1,j+1); //note that this is inverted due to coordinate system differences
                }
        }
    }

    class JuliaSetGUI extends JFrame {
        final JuliaSet set;
        JuliaDrawPanel panel;
        JTextField creal,cimag;
        JuliaSetGUI (JuliaSet setToUse) {
            super("Julia Set GUI");
            this.set=setToUse;
            setDefaultCloseOperation(EXIT_ON_CLOSE);
            setPreferredSize(new Dimension(set.resolution+50,set.resolution+150));
            setLayout(new FlowLayout());
            panel= new JuliaDrawPanel(set);
            add(panel);
            creal=new JTextField(10);
            cimag=new JTextField(10);
            add(creal);
            add(cimag);
            JButton setc=new JButton("Set c value");
            setc.addActionListener(new ActionListener(){
                public void actionPerformed(ActionEvent e){
                    set.c=new Complex(Double.parseDouble(creal.getText()),Double.parseDouble(cimag.getText()));
                    set.run();
                    panel.repaint();
                }
            });
            add(setc);
            JButton close=new JButton("Close");
            close.addActionListener(new ActionListener(){
                public void actionPerformed(ActionEvent e){
                    System.exit(0);
                }
            });
            add(close);
            pack();
        }

        public static void main(String[] args) {
            JuliaSet set=new JuliaSet(600);
            if (args.length==2) 
                set=new JuliaSet(600, Double.parseDouble(args[0]), Double.parseDouble(args[1]));
            JuliaSetGUI gui=new JuliaSetGUI(set);
            gui.setVisible(true);
            set.run();
            gui.repaint();
        }
    }

A note on GUI processes and mode of execution

Congradulations! You have already used what is so called a multi-threaded process. Have you noticed that as soon as you call gui.setVisible() things start to happen in parallel. On the one hand the remainin commands in your program following gui.setVisible() will be executed as usual. In that sense that command is not blocking, i.e. the process of your program does not pause at that point until the user closes the window. Instead an additional thread of execution is formed.

This second thread only waits for user interaction in the window (by the help of operating system and windowing system), and responds to user events as you have defined them through action listeners, etc. This additional thread may be called reactive or event driven since it does not have a programmed flow of commands, but instead reacts to some events (i.e. user interaction).

The main thread on the other hand keeps executing the commands in your program. But the whole process does not come to an end (and display the command prompt) until both processes are finished their job.

As part of this new terminology, we will call the main thread of execution as simply main thread. A main thread of execution always exists when you run any program.

Explicit use of main thread

Examine the following program which animates some growing circles on the window drawing panel. Towards the end of the program you will notice an infinite loop which runs a command as Thread.sleep(500). The static method sleep in the Thread class puts the main thread of execution into sleep for the given amount of time, before continuing. In this way the program is able to repeat re-drawing actions necessary for creating an animation. Therefore, for the first time we take control of the main thread, which already existed in all processes when you run any program.

    import java.util.Random;
    import java.awt.*;
    import java.awt.event.*;
    import javax.swing.*;        
    import java.awt.geom.Ellipse2D;

    class Circle {
        double posx, posy, radius, maxRadius, increment;
        Circle(double posx, double posy,  double maxRadius, double increment) {
            this.posx=posx;
            this.posy=posy;
            this.radius=0;
            this.maxRadius=maxRadius;
            this.increment=increment;
        }
        void grow(){
            radius+=increment;
            if (radius>maxRadius) radius=0;
        }
    }

    class DrawingPanel extends JPanel {
        Circle[] circles;
        DrawingPanel() {
            super();
            setPreferredSize(new Dimension(400,400));
            circles=new Circle[10];
            Random random=new Random();
            for (int i=0;i<circles.length;i++) 
                circles[i]=new Circle(random.nextDouble()*400, random.nextDouble()*400, random.nextDouble()*100, 5);
        }

        void growCircles(){
            for (int i=0;i<circles.length;i++) 
                circles[i].grow();
        }
        public void paintComponent(Graphics g) {
            super.paintComponent(g);        
            for (int i=0;i<circles.length;i++) 
                ((Graphics2D)g).draw(new Ellipse2D.Double(circles[i].posx,circles[i].posy, circles[i].radius, circles[i].radius));
        }
    }

    class GrowingCirclesGUI extends JFrame{
        DrawingPanel panel;
        GrowingCirclesGUI(String title) {
            super(title);
            setDefaultCloseOperation(EXIT_ON_CLOSE);
            setLayout(new FlowLayout());
            setPreferredSize(new Dimension(400,500));        

            panel = new DrawingPanel();
            add(panel);
            JButton closeButton=new JButton("Close");
            add(closeButton);

            closeButton.addActionListener(new ActionListener(){
                public void actionPerformed(ActionEvent e){
                    System.exit(0);
                }
            });

            //pack the frame, ready to display
            pack();
        }
        public static void main(String[] args) {
            GrowingCirclesGUI mainFrame = new GrowingCirclesGUI("Growing Circles");
            mainFrame.setVisible(true);
            try {
              while(true) {
                Thread.sleep(500);
                mainFrame.panel.growCircles();
                System.out.println("grown");
                mainFrame.repaint();
              }
            } catch(Exception e) {
                System.out.println(e);
            }
        }
    }