Event Handling Basics

In a previous section we looked at the important role the Event Dispatch Thread (EDT) plays in Swing, specifically how it keeps the Java Virtual Machine (JVM) process from exiting prematurely, and how the creation and modification of Swing classes must be done by the EDT to ensure thread safety. However, there are some related questions that have been raised directly or indirectly that a Swing developer needs to know about. For instance, what's the correct way to stop the JVM once a Swing application has started? Forcing the application's user to manually "kill" the JVM process clearly isn't an appropriate way for an application to behave. And for that matter, does the EDT continue running indefinitely once started or is there some way to cause it to terminate?

Terminating A Swing Application

Before figuring out how to terminate a Swing application we need to decide when it should be terminated. Let's return again to our simple "Hello, world!" application that displays a frame, which is shown again in Listing 1:

import javax.swing.JFrame; import javax.swing.SwingUtilities; public class ThreadSafeHelloWorldLambda { public static void main(String[] args) { SwingUtilities.invokeLater(() -> { JFrame frame = new JFrame(); frame.setTitle("Hello, world!"); frame.setSize(400, 225); frame.setVisible(true); }); } }
Listing 1: A thread-safe "Hello, world!" class using a lambda expression.

When you run this program it displays a window like the one in Figure 1, though again the frame -- specifically the title bar and borders -- may vary somewhat depending upon which operating system you're using.
'Hello, world!' application window
Figure 1: Window displayed when running the "Hello, world!" application running on Windows 7 with Aero.
What we're looking for is a way to determine when the application should terminate itself. Many desktop applications include a File menu with an Exit item that can be selected to terminate the application, but we haven't added any menus or anything else to the frame. The one way of terminating an application that's universal, though, is to click the Close button of the application's frame, so it's appropriate to use the clicking of that button as an indication that our application should be terminated. If you do run this program and click on the Close button, you'll find that it does cause the frame to disappear but the JVM process doesn't terminate. Once again, that's because the JVM is kept alive by the EDT, which is a non-daemon thread. Obviously we need to either have a way to shut down the EDT -- which will allow the JVM to terminate on its own -- or we need to somehow terminate the JVM while the EDT still running.

One option is to use System.exit(), which we saw in the last section is one of only two things that will cause the JVM to terminate, the other being the complete absence of any live (still executing) non-daemon threads. But even if we define code that calls System.exit() we still need a way to have that code executed when the user clicks on the frame's Close button.

Event Notifications

The problem we're faced with is a common one in object-oriented programming: we have an object -- in this case a JFrame instance -- and we want some code executed when a certain event related to that object occurs. Specifically, we want to define code that will be executed when the frame's Close button is clicked. With common problems like this there's usually a standard solution, and this is no exception: the standard solution in this situation is the use of the observer design pattern.
The observer pattern is also sometimes known as "publish / subscribe", although it's arguably the case that the two terms refer to slightly different things. In the context of a Swing, application, however, there's no meaningful distinction to be made between "publish / subscribe" and "observer pattern".
An implementation of the observer pattern maintains a collection of objects referred to as its listeners that are notified when some event occurs, such as a change in the state of the observed object or when some input occurs such as the user clicking on a close button. The object to be observed (the "publisher") also provides methods that can be invoked to add and remove listeners to its listener collection, and an object that wants to be notified of state changes and user input can simply add itself as a listener of the object that experiences the state change or receives notification of user input.

Perhaps the best way to understand this concept is to look at an example of it and see how it's used. In this case we want to have our code executed when a user clicks a frame's Close button, and if you look at the API documentation for JFrame, you'll see that it inherits from the Window class an addWindowListener() method. That method is used to add a listener (subscriber) to a window's collection of listeners, and those listeners are notified when various events occur, such as the user clicking on the Close button.

The addWindowListener() method requires the caller to pass an object that implements the WindowListener interface, and reviewing that interface shows that it defines seven different methods related to changes in the state of a window:

  • windowActivated()
  • windowClosed()
  • windowClosing()
  • windowDeactivated()
  • windowDeiconified()
  • windowIconified()
  • windowOpened()
Each of these methods is passed a WindowEvent object, which is simply an object that provides information about the event that occurred. In this example we won't need any of the information from the WindowEvent object, though. Instead we simply need to provide an implementation of the interface's methods and place inside one of them the code that we want executed when the user clicks on the frame's Close button, and an example of this is shown in bold in Listing 2:

import java.awt.event.WindowEvent; import java.awt.event.WindowListener; import javax.swing.JFrame; import javax.swing.SwingUtilities; public class CloseableHelloWorld { public static void main(String[] args) { SwingUtilities.invokeLater(() -> { JFrame frame = new JFrame(); frame.addWindowListener(new ShutdownRequestListener()); frame.setTitle("Hello, world!"); frame.setSize(400, 225); frame.setVisible(true); }); } static class ShutdownRequestListener implements WindowListener { public void windowActivated(WindowEvent event) { } public void windowClosed(WindowEvent event) { } public void windowClosing(WindowEvent event) { } public void windowDeactivated(WindowEvent event) { } public void windowDeiconified(WindowEvent event) { } public void windowIconified(WindowEvent event) { } public void windowOpened(WindowEvent event) { } } }
Listing 2: An initial WindowListener implementation class.

Our goal is to call System.exit() when the user clicks on the frame's Close button, but which of these methods should we use? It's easy enough to guess that we want to add the code to either the windowClosing() or windowClosed() method, but probably not as obvious which of the two is the better choice. The documentation for windowClosed() says that it's, "Invoked when a window has been closed as a result of calling dispose on the window", which at this point isn't helpful because it's probably not obvious what is meant by "calling dispose on the window". We'll learn more about that later, but for now it's sufficient to know that windowClosed() isn't the method that will be called when you try to close the window.

By process of elimination, then, we're left with windowClosing(), and its documentation sounds a bit more promising, saing that the method is called, "when the user attempts to close the window from the window's system menu".
In fact, as noted in an earlier tutorial, not every window has a system menu. Fortunately this case is a rare exception to the rule: Java's API documentation is almost always correct and reasonably complete.
Unfortunately, that documention is incomplete and slightly misleading, because the method is called when you attempt to close a window from the system menu or from the window's Close button. For clarification, the system menu is the menu that some systems support, such as the one that appears on Windows when you click on the icon on the left side of the title bar as shown in Figure 2:
Popup menu supported on Windows
Figure 2: A popup menu on Windows.
As indicated earlier, the items in the system menu -- when it's even supported -- are equivalent to the buttons on the title bar, so selecting the Close item from the system menu and clicking on the frame's Close button produce exactly the same results. In fact, it's not even possible within the Java code to distinguish between them to determine which one was used. In any case, we've now established that it's the windowClosing() method to which we want to add our call to System.exit(), so we can modify our listener class as shown below:
public void windowClosing(WindowEvent event) { System.exit(0); }
Now if you run this application and click either the Close button or the menu item, the frame will disappear as it did before and the JVM will exit immediately.

Improving the Event Listener

You've now seen your first example of how to write an event listener (observer) for a Swing application, and although it's fully functional there are some improvements that can be made to it. First, implementing the WindowListener required a relatively large amount of code for a small gain: we only wanted to react to one type of event, but we had to implement all seven methods of the interface. Lambda expressions aren't helpful here because WindowListener isn't a functional interface, but Java does include some convenience classes called adapters for interfaces like WindowListener that make these interfaces easier to use.
Reminder: Java 8 introduced support for lambda expressions, and a functional interface is one that has only a single unimplemented method.
Very simply, an adapter for a listener interface is just a class like our original implementation of ShutdownRequestListener: it implements each method in the interface but doesn't do anything inside any of the implemented methods. This is useful because we can extend the adapter class and override only the method we care about, which in this case is windowClosing(). And by combining an adapter with an anonymous inner class we can greatly reduce the amount of code needed to achieve the results we wanted, as shown in Listing 3:

import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import javax.swing.JFrame; import javax.swing.SwingUtilities; public class CloseableHelloWorld { public static void main(String[] args) { SwingUtilities.invokeLater(() -> { JFrame frame = new JFrame(); frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent event) { System.exit(0); } }); frame.setTitle("Hello, world!"); frame.setSize(400, 225); frame.setVisible(true); }); } }
Listing 3: A WindowListener created using an adapter.

In reality, for this simple application there's no need for us to define and add a listener at all because JFrame includes a built-in listener and provides you with the ability to choose from a list of actions the listener will take when the window closing event occurs. To specify what should happen when the frame's built-in listener is notified of a window closing event, call the setDefaultCloseOperation() method and pass it a parameter that indicates the action to take. That parameter must correspond to one of the values defined in the WindowConstants class:

HIDE_ON_CLOSE is the default and indicates that the window the user asked to close should be made invisible (that is, "hidden") but should otherwise have its state left unchanged. This explains the behavior we saw earlier where clicking on the Close button made the frame disappear but didn't terminate the JVM.

DO_NOTHING_ON_CLOSE does exactly what its name implies and ignores requests to close the window.

DISPOSE_ON_CLOSE once again refers to a window being "disposed", which will be explained shortly but isn't what we want at this point.

Once again, by process of elimination we're left with EXIT_ON_CLOSE, which causes System.exit() to be invoked, so it's functionally identical to the explicit invocation of that method made by our previous listener implementations.

With this new information we can now simplify our application even more, reducing what was originally a large change down to a single like that calls setDefaultCloseOperation() as shown in listing 4:

import javax.swing.JFrame; import javax.swing.SwingUtilities; import javax.swing.WindowConstants; public class CloseableHelloWorld { public static void main(String[] args) { SwingUtilities.invokeLater(() -> { JFrame frame = new JFrame(); frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); frame.setTitle("Hello, world!"); frame.setSize(400, 225); frame.setVisible(true); }); } }
Listing 4: Specifying the action taken by the built-in listener when a close request occurs.

Given how easily we were able to produce the desired results using setDefaultCloseOperation(), the earlier implementations where we created our own custom WindowListener class might seem pointless, but in fact they're not. It's very common in real-world Swing applications to create a listener like this for a frame, because you'll often need more complex handling of a close request than to simply terminate the JVM or hide or dispose of the frame. Again, closing the frame typically is interpreted to mean that you want to close the application, so if you designed a frame that contained some sort of unsaved information you'd typically want the frame to prompt the user to save their work before terminating the application. In that case, you'd need to define a custom window listener like we did earlier, implement the behavior you want (such as prompting the user to save their work or exit without saving), and set the default close operation to DO_NOTHING_ON_CLOSE so that the window's built-in listener won't terminate the JVM when the user asks to close the window. In other words, it's usually only in trivial applications like this one that it's appropriate to allow the built-in listener to handle the closing of a window. In practice you'll instead "disable" the built-in listener using DO_NOTHING_ON_CLOSE and add your own listener to handle the close request.

In reality, none of the implementations we've seen so far is appropriate, because calling System.exit() causes the JVM to terminate abruptly and is rarely appropriate for use in a real-world application. Instead, you should design your code to perform a "clean" shutdown when it's appropriate to do so, and in a later tutorial we'll see how this can be done, keeping in mind that we resorted to calling System.exit() only because the EDT was preventing the JVM process from ending.