[ANU] [DCS] [COMP2100/2500] [Description] [Schedule] [Lectures] [Labs] [Homework] [Assignments] [COMP2500] [Assessment] [PSP] [Java] [Reading] [Help]
COMP2100/2500
Lecture 12: Graphical User Interfaces IISummary
How to program graphical user interfaces in Java with Swing.
Introduction
All the information in these lecture notes is intended as an introduction and a guide to getting started with Java/Swing. It is not intended to be definitive. I am new to this, and I may get some things wrong. What's more likely is that what I say will be correct but limited. For a fuller understanding, use these notes as a starting point or a springboard to get you into the definitive documentation on the Sun website. All the classes you will be using for GUI programming are documented in the Java 1.5 API documentation, or for Macintosh users, the Java 1.4 API documentation. Also very useful indeed (although probably too big to just work through from beginning to end) is the Java Swing Tutorial. I will occasionally give links to individual pages in the Swing Tutorial where you can find more detail.
First example: MouseSpy
This program is adapted from the MouseSpyApplet on pages 396–398 of Big Java by Cay Horstmann (Wiley, 2002).
You can download the source code MouseSpy.java and try the program yourself.
The program puts a window on the screen and then catches all relevant mouse events and prints information about them on the standard output (System.out).
Here is the main program:
public static void main(String[] args) { JFrame frame = new JFrame(); MouseSpy listener = new MouseSpy(); frame.addMouseListener(listener); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JLabel label = new JLabel("Hello world"); JPanel panel = new JPanel(); panel.setPreferredSize(new Dimension(300, 300)); panel.add(label); frame.setContentPane(panel); frame.pack(); frame.setVisible(true); }Let's go carefully line-by-line through it first.
JFrame frame = new JFrame();This creates a new object of class JFrame, which is an application main window. Frames know how to display themselves and their contents, how to minimise, maximise, move and change size. For more about frames, see How to make frames (main windows) in the Swing Tutorial.
Let's ignore the next two lines for now. We'll come back to them later.
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);This tells the program what to do if the user closes the window (by clicking in the little X in one of the top corners usually, although the details of the appearance of the window and its decorations depends on the system and the window manager). Here we're saying that closing this window should quit the application. There are other possibilities: check the Javadoc documentation for class JFrame for more.
JLabel label = new JLabel("Hello world");This creates a label, which is a simple widget that can display a bit of text or an image, or both. This one just displays text. The constructor we've called sets the text of the label to the string “Hello world”. For more about labels, see How to use labels in the Swing Tutorial.
JPanel panel = new JPanel();Another object creation. A panel is a container that you can pack other widgets into. We probably don't really need one here, but it helps with the next step. For more about panels, see How to use panels in the Swing Tutorial.
panel.setPreferredSize(new Dimension(300, 300));This sets the preferred starting size of the panel. You can also tell a frame what size it should be, but this doesn't always have any effect. Try modifying the program to see. Anything that inherits from class java.awt.Component (including class JFrame and class JPanel) has a method setSize() that can take two integers or one Dimension object. Anything that inherits from class javax.swing.JComponent (including class JPanel) also has a setPreferredSize() method, but this only accepts a Dimension object. That's consistency for you...
Sometimes setting the size of a widget works as expected, sometimes it doesn't. There's bound to be a perfectly good reason, but life is too short to read every word of the documentation for literally thousands of classes. This is one of the hardest things about GUI programming. A good beginner's strategy for getting around it is to reuse chunks of code from other programs that work. Eventually you will want to understand everything you're doing, but initially you need to have some small successes, and reusing a few lines that work is a good way of getting that.
panel.add(label);This puts the label inside the panel. This hides a detail: whenever you put widgets inside a Container (such as a JPanel) the way they are arranged is controlled by a Layout Manager. If you don't specify, then the default is what's called a Flow Layout, which means that components are packed in from left to right, top to bottom. This usually isn't what you really want. Later we'll see examples of some other layout managers: border layout and grid layout. For more about layout managers, see Laying out components within a container in the Swing Tutorial.
frame.setContentPane(panel);This makes the panel be the main display area of the frame (the “content pane”). Frames also have separate, specially defined areas at the top for menus and (I think) at the bottom for status bars or whatever.
frame.pack();This is when the actual sizes and positions of everything in the window gets worked out. If you change the contents of a container and don't do this, the appearance doesn't change, which can lead to a lot of frustration. So if something isn't working, try repacking the frame and see if that fixes it.
frame.setVisible(true);I think you can guess what this one does. Initially when created, the frame is invisible. This is so that users don't see the messy process of adding components to the frame. Once everything has been put together and packed, then you make the whole masterpiece visible as the last step.
The code we've just been through puts a window on the screen, with a message in it saying “Hello world”. It's our first Java GUI program. You can type these instructions in the DrJava interactions window if you like. (If you do that, you will need to first type
import java.awt.*; import javax.swing.*;or it won't know what a JFrame is, or JLabel or a JPanel.) Then you can play with the window, change these lines and see the effect. If you want to do this, it's probably a good idea not to set the close operation as above, because if you do, it will reset the interactions pane every time you close the window you made.
Now let's look at the rest of the program. This part catches mouse events (mouse button down, mouse button up, mouse clicked, mouse enters the window, mouse exits the window) and writes a message about each one.
There are four main steps in programming something to happen in response to a particular user event on a widget:
Create the widget. Depending on what type it is, you may want to pack it into a container, or pack other widgets inside it or whatever. Then display it on the screen.
Create a ‘listener’ object to receive the event. This object must implement a listener interface (or extend one of the pre-supplied do-nothing listener classes) from the package javax.swing.event or java.awt.event (usually the latter). You can create a special object of a special class written just for this purpose, or you can add this capability to an existing object by adding extra code to its class.
Connect this listener with the GUI component (widget) it's listening for events on. In this case we're listening for events on the main window, but it can be pretty much any component. Later we'll see buttons: there you attach the listener to the button, a different listener for each button usually.
Write the code that must be executed when the event takes place. This must be in a special method specified by the listener interface. In most cases (where the listener is an ActionListener) this is just called actionPerformed(), but in the case of our mouse listener, there are several routines we have to implement, one for each different type of mouse event: down, up, click, enter & exit.
For more detailed and definitive information on this, see Handling events in the second example program in Learning Swing by example in the Swing Tutorial. For the full detail, see Writing event listeners in the Swing Tutorial.
OK, so here's the code. You've seen the main() method that sets up the GUI display. It's all inside a class that implements the MouseListener interface. It has the following methods:
import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import java.awt.*; import java.awt.event.*; public class MouseSpy implements MouseListener { public void mousePressed(MouseEvent e) { System.out.println("Mouse pressed at (" + e.getX() + ", " + e.getY() + ")"); } public void mouseReleased(MouseEvent e) { System.out.println("Mouse released at (" + e.getX() + ", " + e.getY() + ")"); } public void mouseClicked(MouseEvent e) { System.out.println("Mouse clicked at (" + e.getX() + ", " + e.getY() + ")"); } public void mouseEntered(MouseEvent e) { System.out.println("Mouse entered at (" + e.getX() + ", " + e.getY() + ")"); } public void mouseExited(MouseEvent e) { System.out.println("Mouse exited at (" + e.getX() + ", " + e.getY() + ")"); } public static void main(String[] args) { ... } }Most of this should make sense to you by now. Look up MouseListener and MouseEvent in the API docs if you want to know more.
Finally let's go back to the two lines we skipped in main().
MouseSpy listener = new MouseSpy();This creates an object belonging to our new class MouseSpy.
frame.addMouseListener(listener);This is where we make the association between the listener and the component it listens for events on.
Now if you haven't already, compile and run the program. See what happens when you move the mouse over the window, click it, drag it etc.
Note: When I was playing with this program I discovered that it behaves differently on the Linux machine in my office and on my Mac at home. On my Mac if I press the mouse button and drag then release, I see a “Mouse pressed” event at the starting point and a “Mouse released” event at the end point of the drag. But on Linux I also see a “Mouse clicked” event at the endpoint. What does it do on Windows? (Anyone?) The Mac interpretation agrees with my intuitive idea of what “clicked” means: namely mouse down and up in pretty much the same spot. (On the Mac, to count as a click, the press and release have to be on the exact same pixel, but I can imagine wanting to relax that to allow for a little bit of slip. But not relaxing it completely like Linux does to allow down and up in any locations whatsoever. It's worth checking things like: if you press the mouse on a button and then change your mind, can you move off the button before releasing and have it not count as a click on the button? It wouldn't be good if this varied from one platform to another.) It's probably not super-important, but it's the sort of tricky little detail that you need to watch out for. Yes, Java programs will run on all major platforms, but you should still test that they work as expected on all those platforms rather than just assuming that they will.
Example 2: Tally counter application
Here are four versions of a GUI for the tally counter example of Lecture 4.
Tally counter version 1
Here's what the first version looks like when it's running on my Linux machine in my office. It looks a little different on my Mac because the window decorations are different.
Notice that the window is divided into three parts. The top part has a label displaying the current value of the counter. The middle part is a button that should increment the counter, and the bottom part is a button that should reset the value of the counter to zero. The program uses an object of class Counter as the Model (remember the Model-View-Controller architecture). What we have to do is write the View and Controller parts. For this program we'll put the View and Controller code all together because that seems to be a little easier to understand.
Let's go carefully through the code for the first version:
import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.event.*;This is a bit of laziness or overkill depending on how you think about it. By importing everything from all four packages, I can write any GUI code I like without running into any errors.
class CounterApplication extends JFrame {In the first example the program has a frame but the class was actually a mouse listener. Here I use a slightly different approach, where our GUI class is a frame.
Counter counter; JLabel label; JButton incrementButton; JButton resetButton;Only the counter (the Model) really needs to be declared as a field. The GUI components could have been made local to the constructor since they're never referred to anywhere else.
public CounterApplication() { counter = new Counter();We're in the constructor. First thing we need is to create the Model.
label = new JLabel("" + counter.getCount(), SwingConstants.CENTER);The second argument says where the string will be positioned within the area of the label. Class JLabel has several constructors with different combinations of arguments. This one takes a string and an integer representing a layout position.
incrementButton = new JButton("Increment"); resetButton = new JButton("Reset");OK, here is where we create the buttons. For more on buttons, see How to use buttons, check boxes and radio buttons from the Swing Tutorial.
Container container = this.getContentPane(); container.setLayout(new GridLayout(3, 1)); container.add(label); container.add(incrementButton); container.add(resetButton);This part does the layout of the label and buttons in the main window. Instead of the default flow layout, here we use a grid layout. See How to use GridLayout from Laying Out Components Within a Container in the Swing Tutorial.
class IncrementButtonHandler implements ActionListener { public void actionPerformed(ActionEvent event){ counter.increment(); update(); } }OK, this is something new. What is a class definition doing right in the middle of the code for the constructor?
This is what is called an inner class. It's a full Java class that just happens to be defined right in the middle of another class. When the Java compiler compiles this, it produces a separate .class file CounterApplication$1IncrementButtonHandler.class for this class.
As an Eiffel programmer I found inner classes confusing at first (and to tell the truth I still do...) but in situations like this, they are very convenient. The inner class “knows” about all the local variables and fields that are in scope at this point, which saves quite a few lines of code.
This inner class has a name (which they don't all have to have). In this form it's the closest to what it would be like if it was in a separate file. We'll see some examples of other ways to use inner classes later.
Finally, what does this inner class do? It's an action listener: a listener that listens for an action. For a button, the action is that it is clicked on. It turns out that for most GUI components, the listener you need is an action listener, which is pretty much the simplest sort of listener. It only has one method you have to implement: actionPerformed(). Here when the user clicks on the increment button, we want to increment the counter and then update the display.
IncrementButtonHandler ibh = new IncrementButtonHandler(); incrementButton.addActionListener(ibh);These two lines create an increment button handler and then add it to the list of action listeners on the increment button.
class ResetButtonHandler implements ActionListener { public void actionPerformed(ActionEvent event){ counter.reset(); update(); } } ResetButtonHandler rbh = new ResetButtonHandler(); resetButton.addActionListener(rbh);The same thing for the reset button. The code is almost identical except that this resets the counter instead of incrementing it.
setSize(150, 450); setVisible(true); }Say how big the main window should be, and then make it visible. Notice that there is no target. (It's not frame.setSize(), just setSize().) That means the target is this, the current object, which is the application's main window since this class inherits from classs JFrame.
Notice also that there was no call to pack(). I think you only have to call pack() with some layout managers, not others. I'm pretty sure you need it for border layout and flow layout, but apparently not for grid layout. If you want to know the details, look it up in Laying Out Components Within a Container in the Swing Tutorial.
public void update() { label.setText("" + counter.getCount()); repaint(); }This method updates the display. It first gets the current value from the Model, and makes that the new text of the label. Then it calls repaint() on the main window (this). This causes the window and its contents to be redrawn. You need to call repaint() on a component when the application's internal state has changed and it needs to be redrawn to reflect that change.
public static void main(String[] args){ CounterApplication app = new CounterApplication(); app.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } }Finally the main program. This just creates a new main window object (since this class is called CounterApplication) and sets what it should do when the user closes it. We could even have put that line into the constructor too.
Tally counter version 2
The only real difference here is that I rewrote the program so that it doesn't use inner classes. Here's one of the button listeners as a separate class in its own file:
import java.awt.event.*; class IncrementButtonHandler implements ActionListener { private Counter counter; private CounterApplication view; public IncrementButtonHandler(Counter c, CounterApplication a) { counter = c; view = a; } public void actionPerformed(ActionEvent event){ counter.increment(); view.update(); } }Notice the extra code I had to write. Now that it no longer has access to the internals of class CounterApplication, the button handler needs to store and initialise references to the Model (counter) and the View.
The only other difference is that in class CounterApplication I did the creation of a listener and linking it to the button in one line rather than two:
incrementButton.addActionListener(new IncrementButtonHandler(counter, t his));The handler doesn't even get a name. It is anonymous.
Tally counter version 3
The only difference in version 3 is that I added tool tips to the three components in the main window. This is easy:
label.setToolTipText("Current value of the counter"); incrementButton.setToolTipText("Increment the counter"); resetButton.setToolTipText("Set the counter back to zero");
Tally counter version 4
In version 4 I added a title to the main window, and also a menu bar with two menus and mnemonics (keyboard shortcuts). Here's what it looks like now:
Adding the title is just one line:
setTitle("Counter Application");Adding the menus is a little more work. Here are the basic steps:
Create a JMenuBar and link it to the window.
Create one or more JMenu objects and add them to the menu bar.
Create one or more JMenuItem objects and add them to the menus.
Write an action listener class for each menu item, with the appropriate action coded into its actionPerformed() method.
Create a listener object for each menu item.
Link each action listener to its menu item.
For menus and menu items, you can set the keyboard shortcut with setMnemonic(). Once you've done this, you can use the keyboard to activate the menus. The exact keys involved varies from one system to another. On my Linux machine I can increment the counter by typing Alt-A followed by I. On the Mac it's probably the Apple key instead of the Alt key.
Here's the code for adding the menus to the counter application. I've done each listener in a different way. The first one (for the Exit item in the File menu) is done with a named inner class, like in the first version of the counter application. The second one (for the Increment item in the Action menu) is done with an anonymous inner class. Neither the class nor the object has a name. The third one (for the Reset item) is done as a separate class in a separate file. You can judge for yourself which method you prefer. Does the shorter code sacrifice readability? Or not?
JMenuBar menuBar = new JMenuBar(); this.setJMenuBar(menuBar); JMenu fileMenu = new JMenu("File"); fileMenu.setMnemonic('F'); JMenuItem exitItem = new JMenuItem("Exit"); exitItem.setMnemonic('x'); // Handler implemented as named inner class class ExitItemListener implements ActionListener { public void actionPerformed(ActionEvent event) { System.exit(0); } } exitItem.addActionListener(new ExitItemListener()); fileMenu.add(exitItem); menuBar.add(fileMenu); JMenu actionMenu = new JMenu("Action"); actionMenu.setMnemonic('A'); JMenuItem incrementItem = new JMenuItem("Increment"); incrementItem.setMnemonic('I'); incrementItem.addActionListener( // Anonymous inner class to handle exitItem event new ActionListener() { public void actionPerformed(ActionEvent event) { counter.increment(); update(); } } // end anonymous inner class ); // end call to addActionListener actionMenu.add(incrementItem); JMenuItem resetItem = new JMenuItem("Reset"); resetItem.setMnemonic('R'); // Handler implemented in separate class resetItem.addActionListener(new ResetItemHandler(counter, this)); actionMenu.add(resetItem); menuBar.add(actionMenu);
[ANU] [DCS] [COMP2100/2500] [Description] [Schedule] [Lectures] [Labs] [Homework] [Assignments] [COMP2500] [Assessment] [PSP] [Java] [Reading] [Help]
Copyright © 2005, Ian Barnes, The Australian National University
Version 2005.4, Sunday, 24 April 2005, 22:49:47 +1000
Feedback & Queries to
comp2100@cs.anu.edu.au