Natan T. Cliffer and Panayiotis Mavrommatis
Swing is a graphical toolset for Java applications. It allows platform
independence, meaning that the application you create will look
roughly the same on all platforms
Creating a GUI involves significant design. For this lab, we will focus on the design and implementation of layouts, event handling and drawing. First, we would need to learn how to generate some more basic objects, like a window or a text label.
You may use this document as a tutorial or as reference guide that you can look at whenever you need. We recommend that you try to carry out the (few) assignments so you gain some hands-on experience with Swing. The files you will need are here. You can copy them to your Athena workspace using:
cd ~/6.170/workspace/
mkdir lab2
cd lab2
cp -r /mit/6.170/www/labs/lab2/* .
(be sure to run the above command exactly, including the dot and the spacing)
In case you want to import the files to a project of yours in eclipse, you can do the following:
As you go through this list, take a look at the Swing API for each of these classes. Pay attention to the class hierarchy, and familiarize yourself with the methods of the parent classes as well. The following diagram summarizes the hierarchy for most of the java.awt (old, platform-dependent graphic toolset) and javax.Swing classes. The classes that start with a J, along with some others, belong to the Swing toolkit and are shown in light yellow in the diagram.
All of Swing's classes are Containers -- technically, you can add any component to any other component. In practice, however, many components have this inheritance as a vestigial property, and don't actually expect to have anything added to them. An interface is built up by adding components to a window's pane, or to a nested series of panels which then get added to the pane.
Within each component that functions as a container the layout of the children is based on a layout manager. We'll talk more about these later.
Almost always, you will need to import Swing and awt classes in your code. So you can go ahead and import all of them if you wish, using:
import javax.swing.*; import java.awt.*;
Windows are implemented as JFrames in Swing. This code creates a JFrame:
JFrame myFrame = new JFrame("Title");
myFrame.setBounds(200,200,300,400);
myFrame.setVisible(true);
You should not try to add anything directly to a JFrame. Instead, to make components appear in a JFrame, use JFrame.getContentPane().add(yourComponent)
A JPanel is a simple, empty container meant to be used as a container for other components. Every JPanel has its own Layout, an object that describes how the components will be placed on it. Here is an example of how to create a JPanel whose Layout is initially a FlowLayout (by default), and then changes to a BorderLayout.
JPanel myPanel = new JPanel(); JPanel.setLayout(new BorderLayout());
A JLabel is used to create a single uneditable line of text. The user may not change this text. However, you can change it programatically with JLabel.setText():
JLabel myLabel = new JLabel("This is a Label");
myPanel.add(myLabel);
A JButton is used to create a button:
JButton myButton = new JButton("Press me");
myPanel.add(myButton);
A JTextField is used to create an area displaying a single line of editable text.
A JTextArea is used to create an area displaying some text. This area can be edit able by the user (true by default). Another useful property is the line wrapping, which is false by default. In this lab, we use a JTextArea to display the textual description of the weather.
JTextArea myArea = new JTextArea(); myArea.setEditable(false); myArea.setLineWrap(true);
A JScrollPane is a useful JComponent that allows us to surround an area (a JTextArea for example, or an image) with scroll bars. The constructor of JScrollPane takes the JComponent that we want to surround with the scroll bars, and two integers, that correspond to whether we want the vertical and horizontal scroll bars to be never, as needed or always visible.
JScrollPane myScrollPane =
new JScrollPane(myArea,
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
scrollPane.setPreferredSize(new Dimension(150, 200));
myPanel.add(myScrollPane);
A Timer is used to fire an ActionEvent after a specific delay. For information about ActionEvents, see the Events section. Using a Timer we can implement, for example, an animation. In this lab, the Timer is used to implement an automatic update of the weather every minute. This is the code used: (UpdateListener is an ActionListener we defined that will listen for ActionEvents fired by timer and other JComponents.
int delay = 1000*60; //1 minute Timer timer = new Timer(delay, new UpdateListener()); timer.start();
WARNING: this section refers to javax.swing.Timer. java.util.Timer also exists, but is not designed for use with Swing user interfaces
The first step in GUI creation is design. Draw a picture of the
interface you want. Then, decide on which layout managers you wish
to use and divide the picture in regions. Recursively divide the
rest of the regions until you have either a single component, or a
simple panel of components.
In this lab, we have used BorderLayout and FlowLayout. There are
other useful layouts such as BoxLayout , GridLayout and
GridBagLayout, and you can take a look at their usage in Sun's
Tutorial on Layouts. Even though you will not design the layouts
in this lab, you will be doing so in Problem Set 6 and the Final
Project. You are therefore welcome to follow our approach to the
design of the layouts for this simple GUI.
| Drawing the GUI. We used a basic drawing program to make a simple sketch of how we would like the GUI to appear. You could also draw a sketch by hand. |
|
| First, divide the drawing into regions. Decide on which layout managers to use. We chose BorderLayout and FlowLayout. | ![]() |
| The contentPane is the area of the JFrame we can modify. We access it using the getContentPane() method of a JFrame object. In general, BorderLayout divides a region in five areas: North, South, East, West and Center. We use the three of them to put our components in. In Center, we will place an instance of a MainPanel, which is a class we defined that extends JPanel. The South will also be JPanel, and it will in turn have its own layout. | ![]() |
|
Now we divide displayed to two regions. The top part contains a
JLabel
for the title of our application and the center is another
JPanel.
Moreover, panel6 is split in two parts, with two JPanels on North and South containing the buttons and status bar respectively. Both will have FlowLayouts, i.e. all their components will appear in their normal size, filling the space row-by-row. |
![]() |
| panel1 contains panel2 and panel3. panel2's layout is a FlowLayout, so its components (which is just one JLabel in this case) will appear as described above. | ![]() |
| Finally, panel3 can also have BorderLayout with one component in the West and one at the East. Thermometer and Clouds are two classes that extend JComponent. | ![]() |
When a combo box's selection changes, the ActionListeners registered with it are triggered. We've written an ActionListener for changing the city when the city selection in our weather program's combo box changes. Take a look at NewCityListener in the source code, and understand what it does.
Whenever a button is pressed, JButton calls the actionPerformed method on all ActionListener registered with the button. We've will register the updating functionality of our WeatherPanel with the update button, so it will do what we want.
First, we need a class to implement the ActionListener interface. Create an internal class in MainFrame.java that implements ActionListener. Our WeatherPanel is called displayed; have the actionPerformed() method of your ActionListener call the update method on displayed. Also, have it call updateStatus(), which will update the status bar. You can use the NewCityListener in MainFrame as an example of how to implement such a class.
Now we have the action. The next step is to tie it to the button. In the constructor for MainFrame, add your listener to the update button. Now, when you press ``update'', the weather will update itself.
Other types of listeners can be registered. Look for methods of the form addFooListener if you want to deal with the user doing foo to your object. Examples are addSelectionListener, addMouseListener, addKeyboardListener, and more. Some of these types of listeners are more complicated to write than others; look in the API for details.
A nice technique of drawing on the screen is by creating our own classes that extend JComponent (which is an empty component) and overriding their public void paint(Graphics) method. In that case, whenever we add our component to a container such as a JPanel or a JFrame, assuming that we have not messed up with their own paint() methods, the component will be drawn on them. In short, if you want to create a frame that at some point draws something like a circle or a thermometer, don't change your frame's paint() method. It's better to extend JComponent, change its own paint() method and add an instance of your class to the frame.
You should not, however, call the paint() method yourself. In general, the Swing library takes care of figuring out when components should be painted. When you're making an animation, you can use the repaint() methodto let Swing know to schedule that component for redisplay.
That's exactly what we have done here. Thermometer and Clouds extend JComponent, and their paint() methods are overridden. The method takes a Graphics object as an argument, which is simply what we will be drawing on. Some of the methods for drawing simple graphics in Java, found in the Graphics class and are summarized here:
There are other methods of course, such as drawString,
drawImage and drawPolygon that do what they are
supposed to do.
Assignment
We have a skeletal (actually almost complete) implementation of
the thermometer. Open lab2/gui/Thermometer.java and goto the line with the
comment TODO: Your implementation here.
At that point, the thermometer is drawn and you need to draw a red
column representing the column in an actual thermometer. You can
take a look at the staff implementation to get a feeling of how
this looks like. Basically, you have to draw a red rectangle,
whose height is a linear function of the temperature.
The rectangle should be placed 45 pixels to the right of the
origin, and its bottom edge should be 150 pixels down from the
origin. Its height should be linear in respect to the temperature,
and we have designed it so that every pixel corresponds to 1
degree Fahrenheit. Notice that the very bottom is -30 degrees.
Drawing images from files in Java is also fairly easy. First, we need to load the image from the file. One way to access external files is to use the Toolkit and ClassLoader classes. This allows us to access files that might be included in a jar file instead of the standard file system. As an example, the following code loads an image called "storm.gif", located in the lab2/gui/images directory of our default toolkit.
Toolkit toolkit = Toolkit.getDefaultToolkit();
this.image = toolkit.getImage(
ClassLoader.getSystemResource("lab2/gui/images/storm.gif"));
ImageObserver
When loading an image, we might want to monitor the progress of the this process, which might be useful for example if we are downloading a picture from the web, or even from a local resource. The ImageObserver class is a Swing class that exists for this purpose, and it is used as an argument to many methods such as Graphics.drawImage(...). For our purposes, you can pass this as an argument to methods that require an ImageObserver, even if this is not explicitly created for this purpose.
Assignment
In the public void paint(Graphics) method of Clouds, use the
drawImage(Image image, int x, int y, int width, int height, ImageObserver io)
method of Graphics to draw the image stored in image. You may find the
getWidth(ImageObserver) and getHeight(ImageObserver) methods of Image
useful.