Java Swing Tutorial for Beginners; Hello World, Observers, Listeners and Events

In this tutorial we'll take a look at putting together a basic Java Swing (desktop) application. We'll also take a look at one of the most confusing aspects of Swing for beginners; communication between separate components using events.

This tutorial assumes you have a good knowledge of Java; if you don't, check out my extensive free basic Java course right here.

A Hello World Swing App



The basis of a Swing app is a single window that extends the JFrame class. Take a look at the following code.

 
import javax.swing.JFrame;


public class MainFrame extends JFrame {
    public MainFrame() {
        super("Hello World");
        
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setSize(800, 600);
        setVisible(true);
    }
}

 

We've created a class called MainFrame which extends JFrame. Good practice dictates there should only be one JFrame in your app, since you should normally only have one main window. The constructor initializes this window. The call to the super class constructor (the JFrame constructor) sets the title of the window to "Hello World".

The next line sets the "default close operation"; it determines what happens when you close the window by clicking the cross in the corner. In this case we're telling Swing to quit the application when the main window closes, which is what you usually want.

We then set the size of the window to 800 pixels wide and 600 high (adjust to taste!), and finally we make the window actually visible, since an invisible window isn't much use.

All very well, but how do we use this window? For that, we need a class with a main method somewhere. Here's an example.

 
import javax.swing.SwingUtilities;

public class App {

    public static void main(String[] args) {

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new MainFrame();
            }
        });
    }

}
 

Swing App 0



It's actually simpler than it looks. All we need to do to create the window is to create a MainFrame object using "new". We don't even need to create a variable to refer to it, since the constructor of MainFrame will handle everything we need to handle. The only twist is the SwingUtilities.invokeLater. Although the application will generally work without this, Oracle recommends you should run a Swing app in a special Swing thread created by invokeLater. Without this, in theory, there may be some circumstance where it won't work properly.

We're using anonymous class syntax here to run the thread. This actually looks a lot like running a normal thread in Java. You don't really need to understand this in detail, but if you want to understand more about multithreading, you can find my free course on multithreading here.

Adding a Button and a Text Area



We can proceed to add components to our Swing app using the constructor of our JFrame. I won't go into detail about all the various Swing components in this tutorial; if you want a detailed guide to Swing, I hope you'll consider checking out my Mastering Java Swing course here. But we will take a look at a couple of example components.

In the code below, I've added a JTextArea (a big white area where you can type text) to the centre of the main window, taking up most of the space. I've also added a button to the foot of the window. See below the code for explanation.

 
import java.awt.BorderLayout;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JTextArea;


public class MainFrame extends JFrame {
    
    private JTextArea textArea;
    private JButton button;
    
    public MainFrame() {
        // Set the title
        super("Events Example");
        
        // Set the layout. BorderLayout lets you
        // add components at the centre, north, south
        // east and west positions.
        setLayout(new BorderLayout());
        
        // We need a new text area and a new button
        // Set the text on the button to "click me"
        textArea = new JTextArea();
        button = new JButton("Click Me");
        
        // Add the text area in the centre of the window.
        add(textArea, BorderLayout.CENTER);
        
        // Add the button to the bottom of the window (south)
        add(button, BorderLayout.SOUTH);
            
        // Make the app quit when we close the window.
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        
        // Set the window to 800 pixels wide and 600 high
        setSize(800, 600);
        
        // Make the window visible.
        setVisible(true);
    }
}

 

Swing App 1

Notice the button has resized to the width of the entire window. This is because of the way we added it to the window.

When you add components to a Swing window with the JFrame add() method, you have to first set the "layout manager" that you want to use. We're using BorderLayout here, which allows you to add components in the centre of the screen, or at the positions north (top), south (bottom), west (left), or east (right). BorderLayout is really only intended to define the broad outlines of your application, not to add buttons, but I've used it here for simplicity.

After setting the layout manager, you have to specify some further information to the add() method to tell the layout manager you've set which position to add your component at.

Handling Events



We can handle button clicks by adding a "listener" to the button. The listener must be an object of a class that implements the ActionListener interface. There are various possibilities, but I'll simply make MainFrame implement the interface and use it as a listener here.

Take a look at the following code. We've used the addActionListener() method of the button to add the MainFrame itself as a listener to the button (passing in "this"). The MainFrame implements the ActionListener interface, which forces us to add an actionPerformed method. This method gets called when the button is clicked.

In actionPerformed(), we've called the JTextArea append method to add the text "Hello", followed by a newline, to the JTextArea.

 
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JTextArea;


public class MainFrame extends JFrame implements ActionListener {
    
    private JTextArea textArea;
    private JButton button;
    
    public MainFrame() {
        // Set the title
        super("Events Example");
        
        // Set the layout. BorderLayout lets you
        // add components at the centre, north, south
        // east and west positions.
        setLayout(new BorderLayout());
        
        // We need a new text area and a new button
        // Set the text on the button to "click me"
        textArea = new JTextArea();
        button = new JButton("Click Me");
        
        // Add the text area in the centre of the window.
        add(textArea, BorderLayout.CENTER);
        
        // Add the button to the bottom of the window (south)
        add(button, BorderLayout.SOUTH);
        
        // Add a listener to the button.
        button.addActionListener(this);
            
        // Make the app quit when we close the window.
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        
        
        // Set the window to 800 pixels wide and 600 high
        setSize(800, 600);
        
        // Make the window visible.
        setVisible(true);
    }

    /*
     * This gets called when the button is clicked
     * Notice the code
     * button.addActionListener(this);
     * above.
     */
    @Override
    public void actionPerformed(ActionEvent e) {
        textArea.append("Hello\n");
    }
}

 

Swing App 2

Adding Sub-Panels



There are lots more standard Swing components you can use, and there are lots more layout managers you can use. You can find out more about them in my Mastering Java Swing course. But rather than look at more Swing components and layout managers, let's take a look at something that really baffles beginners.

When you layout a Swing GUI application, usually you want to have more than just one panel in your application. For example, we might have a toolbar and various panels containing forms, charts or whatever.

Let's add a panel to our Swing app and add a couple of buttons to it.

In the following code, I've created a class that extends JPanel. JPanel is a useful class for building up panels that we can use to outline the basic areas of our user interface. I've set the background of the panel to red so that it's easily visible here. I've also set the "preferred size".

Swing components have various methods to set the size; setPreferredSize, setMinimumSize, setSize, setMaximumSize ... it's up to the layout manager that lays them out whether to take notice of any of their sizes. Each layout manager has its own idea about what bits of size information to take into account or ignore.

 
import java.awt.Color;
import java.awt.Dimension;

import javax.swing.JPanel;


public class TestPanel extends JPanel {
    
    public TestPanel() {
        // Specify 200 pixels wide and 300 high.
        setPreferredSize(new Dimension(200, 300));
        setBackground(Color.red);
    }

}

 

Now we need to add this panel to the main window. In the following code, I've simply replaced the button we had previously with our new panel. I've also removed the "implements ActionListener" code and removed the actionPerformed method. Finally, I've made the add() method add the panel to the left-hand side of the window (west) instead of at the bottom (south).

 
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JTextArea;


public class MainFrame extends JFrame {
    
    private JTextArea textArea;
    private TestPanel panel;
    
    public MainFrame() {
        // Set the title
        super("Events Example");
        
        // Set the layout. BorderLayout lets you
        // add components at the centre, north, south
        // east and west positions.
        setLayout(new BorderLayout());
        
        // Now we use our panel instead of a button
        textArea = new JTextArea();
        panel = new TestPanel();
        
        // Add the text area in the centre of the window.
        add(textArea, BorderLayout.CENTER);
        
        // Add the panel to the left of the window (west)
        add(panel, BorderLayout.WEST);
        
    
        // Make the app quit when we close the window.
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        
        
        // Set the window to 800 pixels wide and 600 high
        setSize(800, 600);
        
        // Make the window visible.
        setVisible(true);
    }
}

 

This gives us the following:

Swing App 3

Notice the border layout manager ignores the preferred height of the panel; it only takes note of the width. This is because border layout makes the panel fit the height of the window, if we place it on the left.

Adding Components to a Sub-Panel



We can add components to a sub-panel using a layout manager, in exactly the same way that we added components to our JFrame subclass (MainFrame) earlier.

You can find a visual guide to Swing layout managers here. There's also a visual guide to Swing components here.

In the following code, I've used the BoxLayout manager to add two buttons to the panel. This layout manager lets us layout components horizontally or vertically. I've also made the following changes to the panel:


  • Removed the line that set the panel background color

  • Removed the line that set the panel size -- the panel will size automatically around the buttons

  • Made the panel implement ActionListener

  • Added the panel as a listener to the buttons



Plus, I've added code that checks which button was pressed using the getSource() method of the "event" object that's passed to actionPerformed(). This gets us the component that triggered the event.

 
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JPanel;


public class TestPanel extends JPanel implements ActionListener {
    
    private JButton helloButton;
    private JButton goodbyeButton;
    
    public TestPanel() {
        
        // Create the buttons and add text to them.
        helloButton = new JButton("Hello");
        goodbyeButton = new JButton("Goodbye");
        
        // Set the panel itself to listen to the buttons
        helloButton.addActionListener(this);
        goodbyeButton.addActionListener(this);
        
        // Use box layout to add buttons in a vertical column
        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
        add(helloButton);
        add(goodbyeButton);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        
        // We can use the event "source" to figure
        // out which button was pressed.
        JButton source = (JButton)e.getSource();
        
        if(source == helloButton) {
            System.out.println("Hello");
        }
        else if(source == goodbyeButton) {
            System.out.println("Goodbye");
        }
    }

}

 

Swing App 4


If we run this app in Eclipse (or some other IDE), the text "hello" or "goodbye" appears in the console of the IDE when we click the buttons, depending on which button we click.

Goodbye
Goodbye
Hello
Hello





Custom Events with the Observer Pattern



But now we have a problem. Suppose we want to add text to our text area when the buttons are clicked. How can we get the information we need to the text area?

The wrong way to do this would be to pass the text area to the sub-panel. Doing this kind of thing ends up creating a tangled rat's nest, with one component talking to another in ways that end up being hard to trace.

What we need to do is to get some other class, such as MainFrame, to handle all the events that occur in our app. We need to get it to act as a "controller", receiving information and telling the sub-components what to do.

Note that if we only wanted something to change in the sub-panel when the buttons were clicked, there wouldn't be a problem. The problem arises because we want to communicate beteen our custom panel component and a separate component, the text area.

We can create our own event, a bit like ActionEvent. Let's create our own version of the ActionEvent interface, which our MainFrame can later implement. Here it is:

 
public interface MyFormListener {
    public void formEventPerformed();
}
 

Now MainFrame can implement this, adding a formEventPeformed() method which will be called when one of our buttons is clicked.

But how will MainFrame know which button was clicked? We need to specify that the formEventPerformed method can accept some kind of parameter which will carry this information, just like ActionListener specifies that actionPerformed accepts a useful ActionEvent parameter. There are various possibilities here, but we'll go for adding an enum type to the interface, for formEventPerformed() to use.

Here's our new interface:

 
public interface MyFormListener {
    
    public enum Type {
        hello,
        goodbye
    }
    
    public void formEventPerformed(MyFormListener.Type type);
}
 

Now let's give our sub-panel a "setFormEventListener" method. We'll add the following code to our panel:

In TestPanel.java:

 
private MyFormListener formListener;
    
public void setFormListener(MyFormListener formListener) {
    this.formListener = formListener;
}
 

This allows us to "set a listener" to our panel. We'll also change the actionPerformed method, which receives events from our buttons, to raise new events of our custom type, informing the listener what's happening:

Also in TestPanel.java:

 
@Override
    public void actionPerformed(ActionEvent e) {

        // We can use the event "source" to figure
        // out which button was pressed.
        JButton source = (JButton) e.getSource();

        if (formListener != null) {
            if (source == helloButton) {
                formListener.formEventPerformed(MyFormListener.Type.hello);
            } else if (source == goodbyeButton) {
                formListener.formEventPerformed(MyFormListener.Type.goodbye);
            }
        }
    }

 

So the completed panel code looks like this:

 
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JPanel;

public class TestPanel extends JPanel implements ActionListener {

    private JButton helloButton;
    private JButton goodbyeButton;

    private MyFormListener formListener;

    public void setFormListener(MyFormListener formListener) {
        this.formListener = formListener;
    }

    public TestPanel() {

        // Create the buttons and add text to them.
        helloButton = new JButton("Hello");
        goodbyeButton = new JButton("Goodbye");

        // Set the panel itself to listen to the buttons
        helloButton.addActionListener(this);
        goodbyeButton.addActionListener(this);

        // Use box layout to add buttons in a vertical column
        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
        add(helloButton);
        add(goodbyeButton);
    }

    @Override
    public void actionPerformed(ActionEvent e) {

        // We can use the event "source" to figure
        // out which button was pressed.
        JButton source = (JButton) e.getSource();

        if (formListener != null) {
            if (source == helloButton) {
                formListener.formEventPerformed(MyFormListener.Type.hello);
            } else if (source == goodbyeButton) {
                formListener.formEventPerformed(MyFormListener.Type.goodbye);
            }
        }
    }

}

 

Now we can modify our MainFrame class to use this code. We'll do the following:


  1. Make MainFrame implement MyFormListener

  2. Add MainFrame as a listener to the panel (AFTER creating the panel with "new")

  3. In MainFrame's new formEventPerformed method, check the "type" parameter and then take appropriate action, calling "set" methods such as JTextArea append() on sub-components




Here is the completed code.

 
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JTextArea;


public class MainFrame extends JFrame implements MyFormListener {
    
    private JTextArea textArea;
    private TestPanel panel;
    
    public MainFrame() {
        // Set the title
        super("Events Example");
        
        // Set the layout. BorderLayout lets you
        // add components at the centre, north, south
        // east and west positions.
        setLayout(new BorderLayout());
        
        // Create the panel and text area
        textArea = new JTextArea();
        panel = new TestPanel();
        
        // Now we've created the panel, we can listen to it.
        panel.setFormListener(this);
        
        // Add the text area in the centre of the window.
        add(textArea, BorderLayout.CENTER);
        
        // Add the panel to the window
        add(panel, BorderLayout.WEST);
        
    
        // Make the app quit when we close the window.
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        
        
        // Set the window to 800 pixels wide and 600 high
        setSize(800, 600);
        
        // Make the window visible.
        setVisible(true);
    }

    @Override
    public void formEventPerformed(MyFormListener.Type type) {
        if(type == MyFormListener.Type.hello) {
            textArea.append("hello\n");
        }
        else if(type == MyFormListener.Type.goodbye) {
            textArea.append("goodbye\n");
        }
        
    }
}

 

Swing App 5

Notice that our custom event didn't mention buttons; we could change the buttons to checkboxes or menu items or whatever, without altering the event. So the event abstracts away unnecessary detail.

What we've done here is to have the panel raise an event in response to its buttons being clicked. Our MainFrame listens to that event. This is also a form of "event bubbling", sort of, where we are bubbling the button events up to the MainFrame -- except that we haven't actually bubbled up the button action events, but rather transformed them and consolidated them into a new type of event.

This whole thing with events and listeners is known as the Observer Pattern.

Summary



You can download the entire code as a zip here.

In this Java Swing tutorial we've tackled one of the most difficult aspects of Swing for beginners; how to split your application up into separate components and then communicate between those components. There's a lot more to Swing, but if you can get the hang of this, most of the rest is comparatively simple (well, apart from the ferocious GridBagLayout and the intricacies of JTree).

Don't forget, you can find a complete Swing video course, taking you from Swing beginner (assuming only core Java knowledge) to Swing master, right here: Mastering Java Swing