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.
The basis of a Swing app is a single window that extends the JFrame class. Take a look at the following code.
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.
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.
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.
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.
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.
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.
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).
This gives us the following:
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.
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:
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
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:
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:
Now let's give our sub-panel a "setFormEventListener" method. We'll add the following code to our panel:
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:
So the completed panel code looks like this:
Now we can modify our MainFrame class to use this code. We'll do the following:
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.
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