CS 124, Fall 2017
Lab 10 UNUSED LAB: GUI Tutorial

In this lab you will create a simple GUI program that lets the user draw some colored rectangles in a drawing area. The program is not very exciting, but the lab introduces you to some fundamental ideas of GUI programming in tutorial form. You should carefully follow the steps for creating the program, and you should test your program frequently along the way. Most of the code for the program can be copy-and-pasted from this page into your program — but you should try to understand what you copy. (However, you won't understand everything fully until you read Chapter 6.)

This program is the first time that you have used two things: subclasses and nested classes. A new class can be declared to extend another, already existing class. The new class is then a "subclass" of the existing class, and it inherits the properties and behaviors of that class. It is also possible to put the definition of a class inside another class. The first class is said to be "nested" inside the second class. Nested classes make it possible to have more organized code, by chunking related variables and methods together.

There are no files to copy into the project. You will create the necessary files from scratch.

For this program only, you do not need to add any comments to the program.

This lab is due next at the beginning of lab next Thursday, November 9. You will turn in files named RectPanel.java and RectMain.java. We did not end up using this lab!

Panels and Program

You have already seen some of the GUI components that you will use today, in the programs from previous labs. However, the goal now is to understand them better so that you can start constructing your own GUIs from scratch.

The visual building blocks of a Graphical User Interface (GUI) are called components in Java. Often they are referred to as "widgets." A fundamental component is the panel, represented by the Java class JPanel. A panel can be used in two ways: as a place where you can draw, by writing a paintComponent method, or as a container for other components. This program will use several panels. The main panel will be a container that will hold a smaller panel where the rectangles will be drawn. The main panel will also hold other components in addition to the drawing panel.

You need a class to represent the main panel of the program. Create a new class in your project named RectPanel for this purpose. (It can be in any package.) The RectPanel class, like many GUI classes, needs to import Java's standard GUI classes. To do this, you can add the following lines to the top of the file:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

Alternatively, you could import individual classes one at a time as you need them.

A RectPanel will be a special kind of JPanel; that is, it "extends" the JPanel class. To make this true, change the first line of the class definition to read

public class RectPanel extends JPanel {

Your program will use several other classes, which can be defined as nested classes inside the RectPanel class. It is useful to have a class to represent rectangles that have been drawn on the screen. You also need a class to represent the panel where the drawing is done; that class will extend JPanel, just like the main panel class. It would be possible to put these classes in separate files, but we will take advantage of the fact that a class definition can contain nested classes. To define the two classes as nested classes, add the following code inside the definition of the class RectPanel. (There will be an undeclared variable error until you add a declaration for rects in the next step of the lab.) You will not have to make any changes to these classes for the rest of the lab.

 * A nested class to represent colored rectangles.
private class Rect {
    int x1,y1;   // coordinates of one corner of the rect
    int x2,y2;   // coordinates of the opposite corner
    Color color; // the color of the rectangle

 * A nested class to represent the panel where rectangles are drawn.
private class Display extends JPanel {
    protected void paintComponent(Graphics g) {
        super.paintComponent(g); // fill with background color
        for (int i = 0; i < rects.size(); i++) {
               // draw the i-th rect, with a black border
            Rect r = rects.get(i);
            int x = Math.min( r.x1, r.x2 );
            int y = Math.min( r.y1, r.y2 );
            int w = Math.max( r.x1, r.x2 ) - x;
            int h = Math.max( r.y1, r.y2 ) - y;
            g.setColor( r.color );
            g.fillRect( x,y,w,h );
            g.drawRect( x,y,w,h );

You should also add the following instance variables to the RectPanel class. Note that both the Rect and the Display classes are used to define instance variables. These declarations go directly in class RectPanel, not inside the nested classes:

private ArrayList<Rect> rects; // Contains rects that the user has drawn.
private Color currentColor;    // Currently selected color for new Rects.
private Display display;       // The panel where the rects are drawn.

Finally for now, you need a constructor for the RectPanel class. Add the following constructor to the class definition:

public RectPanel() {
    rects = new ArrayList<Rect>();
    currentColor = Color.RED;
    display = new Display();
    display.setPreferredSize( new Dimension(800,600) );
    this.setLayout(new BorderLayout());
    this.add(display, BorderLayout.CENTER);

You will neet to import java.util.ArrayList. This constructor initializes all the instance variables (which could also have been done as part of the declarations of those variables). It configures the display panel by setting its "preferred size," the size that it wants to be, and by setting its background color. The new feature here is that the display panel is "added" to the RectPanel that is being constructed. The add() method is used to add components to a JPanel that is being used as a container. A panel uses a "layout" to set the positions and sizes of the components that it contains. The default layout just lines up the components in a row, but here I want to use a "border layout," so I set the layout for this panel to be a new object of type BorderLayout. A border layout has a large "center" component bordered, optionally, by components to the "north", "south", "east", and "west" of the center. We put the display panel into the "center" position in the main panel by saying this.add(display,BorderLayout.CENTER). Later, we will add another component in the "south" position of the main panel. (Note that the use of "this" in the last two commands is optional, but I want to emphasize that I am calling a method in the RectPanel that is being constructed.)

Even a GUI program needs a main routine. When you run a program, it's the main routine that is executed. For a GUI program, the main routine typically just creates and configures a window and shows it on the screen. The window itself is represented by an object of type JFrame. A JFrame comes with a title . It can hold a "content pane," usually a JPanel, that fills most of the window. It can also hold a menu bar, represented by an object of type JMenuBar.

Although the main routine could go in the RectPanel class, it really doesn't belong there. It makes more sense to put it in its own separate class. So, create a new class named RectMain and add the following main routine to that class:

public static void main(String[] args) {
    JFrame window = new JFrame("Draw Some Rectangles!");
    RectPanel mainPanel = new RectPanel();
    window.setContentPane( mainPanel );

You will need to import java.util.JFrame. When you want to run your program, remember that you have to run the class that contains the main program. Most of the code in the main routine should be reasonably easy to figure out, but note that the command window.pack() sets the size of the window based on the preferred sizes of the components that it contains.

A this point, you should be able to run the program and see a window containing a large, blank, white area.

Mouse Drag

For the next step, we add mouse interaction to the program. The goal here is to let the user draw a rectangle in the display panel by dragging the mouse. The rectangle will have one corner at the point where the mouse is pressed. As the user moves the mouse, a rectangle will be drawn with its second corner at the current mouse position. When the user releases the mouse, the new rectangle becomes a permanent part of the drawing.

When the user interacts with a GUI program, the interaction generates "events." The program can "listen" for these events, which just means that the program can specify methods to be called when the events occur. These methods allow the program to respond to the user's events.

When the user clicks-and-drags with the mouse on a component, a series of events is generated. The action starts with a "mousePressed" event when the user first presses a button on the mouse. This is followed by a series of "mouseDragged" events as the user moves the mouse. Finally, there is a "mouseReleased" event when the user releases the mouse button. To program responses to these events, we need an object to act as a listener. In this program, we will use another nested class for this purpose. Here is a general-purpose nested class definition that can be used to define listeners that implement mouse dragging. The same basic outline could be used in other GUI programs. Add this class definition to the inside of class RectPanel:

 * A class for responding to a mouse drag by drawing a rectangle.
private class MouseHandler extends MouseAdapter {
    boolean dragging; // is the user dragging the mouse?
    public void mousePressed(MouseEvent evt) {
        if (dragging) { // don't start a new drag if already dragging
        dragging = true;
    public void mouseDragged(MouseEvent evt) {
        if (!dragging) { // don't do anything if not dragging
    public void mouseReleased(MouseEvent evt) {
        if (!dragging) { // don't do anything if not dragging
        dragging = false;

To set up listening for mouse events, you need to create an object of type MouseHandler to do the listening, and you have to tell the display panel to send mouse events to that object. This can be done by adding the following code to the constructor of the RectPanel class, after the display has been created:

MouseHandler mouser = new MouseHandler();

The MouseHandler object is added as a listener to the display panel, which means that it will respond to mouse events on that panel. It is added as a "mouseListener," which means it will respond to mousePressed and mouseReleased events. And it is added as a "mouseMotionListener," which means that it will also respond to mouseDragged events.

It only remains to program the methods in the MouseHandler class. When the user presses the mouse, you want to create a new Rect and add it to the list of Rects. Both corners of the new rectangle are placed at the mouse position, given by evt.getX() and evt.getY(). Since both corners are at the same point, this rectangle starts out with size zero and is not visible. The color of the rectangle is copied from the instance variable currentColor, which for now is red. To do all this, add the following code to the mousePressed() method:

Rect r = new Rect();
r.x1 = r.x2 = evt.getX();
r.y1 = r.y2 = evt.getY();
r.color = currentColor;

Note that the new rectangle is the last item in the list of rectangles. This means that it can be referred to as rects.get(rects.size()-1). As the user drags the mouse, the second corner of this rectangle must be set to the new mouse position, and the display must be repainted to show the change. To accomplish this, add the following code to the mouseDragged() method:

Rect r = rects.get(rects.size()-1);
r.x2 = evt.getX();
r.y2 = evt.getY();

For this program, no changes are necessary in mouseReleased().

You should now be able to run the program and draw rectangles by dragging the mouse on the display.

Control Panel with Buttons

It's time to add another component to the RectPanel. We will add another small panel in the "south" position of the RectPanel. This panel will contain a couple of buttons. A button is represented by an object of type JButton. So, we create a new JPanel, add it to the main panel, then create a JButton and add it to the new panel. To do this, add the following code to the RectPanel constructor:

JPanel buttonBar = new JPanel();
this.add(buttonBar, BorderLayout.SOUTH);
JButton clearButton = new JButton("Clear");

The parameter to the JButton constructor is the text that will appear on the button. You can run the program now to see the button, but clicking on it won't do anything yet. When the user clicks a button, an event of type ActionEvent is generated. To program a response, you need to add an "action listener" object to the button. Then, when the user clicks the button, the system will call an actionPerformed() method in the action listener object. To define the action listener, use another nested class inside RectPanel:

 * A class for responding to button and menu events.
private class ActionHandler implements ActionListener {
    public void actionPerformed(ActionEvent evt) {
        String cmd = evt.getActionCommand(); // the text from the button
        if (cmd.equals("Clear")) {
            rects.clear(); // removes all items from the ArrayList
            display.repaint();  // repaint the display to show the change

Back in the RectPanel constructor, you have to create an object of type ActionHandler and add it as an action listener for the button. To do this, add the following two lines at the end of the RectPanel constructor, and then run the program to see that it works::

ActionHandler buttonHandler = new ActionHandler();

Assignment: Add an "Undo" button to the buttonBar panel, and program it by adding some code to the ActionHandler. When the user clicks the "Undo" button, the most recently added rectangle should be removed from the drawing. Note that the ArrayList rects has a method rects.remove(n), which removes item number n from the list. (Of course, there will be an error if n is not in the range of valid indices for the list.)

Menu Bar

Commands in GUI programs are often given using menus rather than buttons. As an example, we will use a menu for setting the current drawing color. Menu items are similar to buttons: When the user selects a menu command an action event is generated, and you can use an action listener to respond to the command. However, a menu item must be in a menu, which must in turn be in a menu bar, and that menu bar must be added to a window — which sounds more complicated that it is. A problem for us is that the window is created in the main program, but the menu bar has to be created and programmed in the RectPanel class. There are a few steps to make this possible...

First, write a method in the RectPanel class to create the menu bar that will be used in the program. Here is the code that adds just one item to the color menu:

 * Create and return a menu bar that can be used with this panel.
public JMenuBar makeMenuBar() {
    JMenuBar menubar = new JMenuBar(); // create the menu bar
    JMenu colorMenu = new JMenu("Color"); // create a menu
    menubar.add(colorMenu); // add the menu to the menu bar
    ActionHandler menuHandler = new ActionHandler(); 
                    // for responding to menu commands

    JMenuItem item; // item to be added to the color menu
    item = new JMenuItem("Blue");  // create the item
    item.addActionListener(menuHandler);  // add the listener
    colorMenu.add(item);  // add the item to the colorMenu

    return menubar;

You also have to program the action listener to respond to the command from the menu item. (Note that we are using the same action listener class that we used for the buttons, which is not especially good style). Setting the current drawing color to blue just means setting the value of the instance variable currentColor to Color.BLUE. So, add the following case to the actionPerformed() method in the ActionHandler class:

else if (cmd.equals("Blue")) {
    currentColor = Color.BLUE;

Finally, to get the menu to appear in the window, you have to add the menu bar to the JFrame that represents the window. To do that, go to the main() routine and add the following line:

window.setJMenuBar( mainPanel.makeMenuBar() );

This line should be added before the window.pack() command, so that the pack will take the size of the menu bar into account when it computes the size of the window.

You should now be able to run the program and use the menu to change the drawing color to blue. After you do that, when you draw a rectangle, it will be blue.

Assignment: Add at least three more colors to the "Color" menu, and make the commands work. You should include red among the colors, so that the user can go back to drawing with the default color.

Dialog Box

It's boring to have only a few drawing colors to choose from. To fix that, we can make it possible for the user to select a custom color of their choice. Java has a standard method for putting up a "color chooser" dialog box where the user can select any color.

To implement custom colors, first add another item to the "Color" menu. The item should read "Custom...". The "..." is a standard way of indicating that the command will call up a dialog box. Then add the following case to the actionPerformed() method in the ActionHandler class:

else if (cmd.equals("Custom...")) {
    Color c;
    c = JColorChooser.showDialog(display,"Select Drawing Color",currentColor);
    if (c != null) {  // (if c is null, the user canceled the dialog)
        currentColor = c;

Try running the program and drawing with a custom color!


For a little extra credit on this lab, you can make one or more of the following improvements to the program (or maybe think up something on your own):