interface
for that (ActionListener) and we hand the button an object that
implements that interface and say "when you are clicked, call
the react-to-a-click method from the click-reactor interface on
this object". Polymorphism then provides the mechanism for
allowing many implementations for that one click-reactor interface.
In this lesson, we're just going to learn how to create the most basic GUI component - a window - and how to react to the most basic user action - clicking the x to close the window. Although that seems very limited, it will show us the basic mechanisms that permeate the whole Java Swing API (the GUI API we'll use).
JFrame.
You create a JFrame like you do anything else: you use new!
JFrame f = new JFrame();However, while this creates the JFrame, it does not display it. It only gets displayed when you set it to be visible.
f.setVisible(true);Now if you create a program with only those two lines, it "works" but is more than a bit underwhelming. The little blip on the line below is all you get. The orange part is the "title bar" of the window, the gray part is the bottom border.
JFrame f = new JFrame();
f.setTitle("IC211 GUI Ex0"); // sets title that appears on the top bar
f.setSize(350,400); // sets the size (in pixels) of the frame
f.setLocation(100,100); // sets the top-left corner of the window on the desktop
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // makes it so that closing window exits program
f.setVisible(true);
With this minimal code you get a Window you can be proud of.
Now, to emphasize the fact that a window is nothing more than JFrame object, let's create 5 windows and pop them all up on the screen at once. The following program will create all five first, wait for the user to enter an 'x', and only then will it actually display the five on the screen.
You'll notice that the "EXIT_ON_CLOSE" line is commented out. Why? With that uncommented, closing any one window exits the program. On the other hand, with it commented out like it is, even when all five windows have been closed, the program is still running. We have to do a ctrl-c in the terminal window to exit it. That's something we'll fix in a bit.
Despite the hiccough of window closing in our example, I hope you'll see that having windows simply be objects like everything else in Java, is pretty nice. We can create them, store them, and manipulate them with methods calls, just as we do for more familiar things like Strings and Exceptions.
setVisible(true)'s, main() is done. Shouldn't the
program stop at that point? The reason the program doesn't stop
at that point is because programs that use the Swing GUI
components (like JFrame) automatically use at least two threads.
A thread is an executing stack of function calls. All
programs you've seen up to this point have been single-threaded,
which means that there is only one executing stack of function
calls. In a multi-threaded program there are more than one
simultaneously executing stacks of function calls. A
multi-threaded program doesn't exit until all threads have
finished executing. So, going back to our Swing program with
the JFrames, we have the main thread (the stack of function
calls with the call to main() on the bottom) and we have a
second thread called the event dispatch thread in which
executes all code related to displaying the GUI and responding
to user actions within the GUI. So, even though the main thread
ends as soon as the setVisible(true)'s are done, the event
dispatch thread continues running ... and so the program
continues running.
The fact that all GUI actions run in this even dispatch thread is going to be an important topic in the future. But for now, we'll comfortably ignore that fact!
The closing of a window is an event, just like button pushes, combo-box selections, or text box changes, all of which we'll consider in the future. So dealing with this one little problem will give us an opportunity to examine the general design of event handling in Java's GUI API. That basic model is quite simple: a GUI component keeps a list of Listener objects that are listening to the various events that can occur with that component, and when an event happens to the component, it calls a method on each of the Listener objects associated with that kind of event, and passes them an Event object that contains information about the details of the event that occurred.
To be concrete, actions on JFrames
generate WindowEvent objects, listeners for these
events are classes that implement
the WindowListener interface. That interface is
WindowListener
{
void windowActivated(WindowEvent e);
void windowClosed(WindowEvent e);
void windowClosing(WindowEvent e);
void windowDeactivated(WindowEvent e);
void windowDeiconified(WindowEvent e);
void windowIconified(WindowEvent e);
void windowOpened(WindowEvent e);
}
... which shows you all the events that might occur.
When you create a class that implements that interface, you
need to define each of these methods, which means providing
the code that you want to execute in response to each of these
actions. For starters, let's simply print out a message
everytime a window closes, just to show that we can react to
these events.
Now, if you are like everyone else in the world, you are
horrified at all the code you have to write just to print out a
message when a window is closed. As a convenience, just to ease
that pain, the Java API has defined a class called WindowAdapter
that implements WindowListener, simpy defining all the methods
with empty bodies, { }. This way, we can define
our WindowDisposer class as extending WindowAdapter and only
override the one method we care about, windowClosed(WindowEvent e);.
The new version of the program below, is functionally the same
as the previous version, but somewhat shorter.
|
Questions: Why must we make WindowAdapter a class? Why couldn't we make it an interface? If you are defining a class "Foo" that you want to add as a WindowListener to a JFrame, what limitations are forced on you if you decide to extend the class WindowAdapter rather than implement the interface WindowListener?
So, what do we take away from this? Java Swing's event handling mechanism is fundamentally about polymorphism and inheritance (or at least the limited form of inheritance offered by interfaces). In fact, it is a prime example of multiple implementations of the same interface, because every time we need to define how to react to an event (like a window closing) we provide a different implementation of the listener interface.So what's the takeaway here? Very often this is how we make GUIs in Java. We don't just use JFrame's like we do Strings and ints, we adapt and extend them to fit the application we have in mind. In any given application, almost all of the JFrame's behavior is simply inherited - i.e. almost everthing works like a generic JFrame - but some crucial parts will change, or some important new functionality will be added. The fact that we can derive new components, like DisposableJFrame, from existing ones is central to the Swing design.