Swing Overview

Swing consists of a large number of classes and interfaces that are linked together through inheritance, inclusion, and implementation. The set of classes and interfaces lets you create windows, respond to events like mouse clicks or key presses, and draw things in a window. Using Swing, you can do just about anything a typical application can do.

In addition to creating windows, buttons, and menus, Swing sets up what is called an event-based program. That means the top level program sits in a loop and waits for things to happen. When the user clicks a button or uses the keyboard, the program responds to that event. Swing itself runs the main loop and handles lots of stuff you don't want to deal with.

Somehow, though, you need to tell Swing what to do in response to certain events. For example, if there is a button that should run one iteration of a simulation, you need to be able to tell Swing what to do--what code to execute--when the user clicks on it. You tell Swing how to respond to certain events by hooking your own methods onto events or visual pieces of a window. For a simple interface widget such as a button, you add something called an ActionListener to the button. The ActionListener has a single method that gets executed only when the user clicks a button. By writing your own version of that function, your own code gets executed on a button click.

The visual part of Swing lets you create a window and place widgets and drawing areas according to a layout plan. The layout is hierarchical, like a tree, with the window itself as the parent of all window elements. Some elements are, themselves, hierarchical. For example, a tool bar is a container object that can include multiple widgets like buttons and text fields. To add a new widget to a window, you create a new instance of the proper class, set up its properties and then add it to the desired parent visual element. When designing your layout, it is useful to draw the tree of elements first in order to establish the proper hierarchy and layout.

The following sections go through some of the specifics of creating a Swing application, including creating a window and responding to events. The complete implementation is in Circles.java

Drawing Circles

The window is at the root of the visual hierarchy. The parent window class is called a JFrame. In order to create your own window, you have to extend the JFrame class by creating a constructor and a main method. The constructor sets up the window layout tree and any action hooks, and the main method creates a window and starts up the run loop.

In order to demonstrate the concepts, we're going to go through the process of creating an application that draws lots of circles in random locations. We'll go through the program in a top-down order to help break down the problem into smaller pieces that are easy to solve.

Our main class will be called Circles. Think of the Circles class as representing the application window. The Circles class extends JFrame in order to inherit all of the window creation and management code.

public class Circles extends JFrame {

The Circles class will have three methods. One is the constructor, one is the main method, and one is a helper method for the constructor. The Circle class will also contain two private classes to implement parts of the window.

The main method is straightforward: create a new instance of the Circles class, pack the layout (traverse the layout tree), and then make the window visible. Almost all of the hard work is done in the Circles class constructor. As part of creating a JFrame object (which Circles inherits), Swing starts up a thread that listens events once the window becomes visible. That thread keeps the program running even after the main method below is complete.

    public static void main(String[] args) {

	int dx = 500;
	int dy = 500;

	// create a window
	Circles vw = new Circles(dx, dy);

	// generate the layout and set the window to visible
	vw.pack();
	vw.setVisible(true);

	// run loop is going now
    }

The constructor for Circles is not much more complex. The first step is to call the constructor for the parent class (JFrame). Giving a string argument to the parent constructor gives the window that title. The second step is to call a method that tells the window to terminate the program when the window is closed. If you have a program that creates many windows, you may not want to do that, but in this case it's the proper behavior. The third step is to create a place on the screen in which to draw and then add the panel to the layout hierarchy by making it a child of the Circles JFrame. The final step is to create a toolbar and add it to the layout hierarchy as a child of Circles.

The first two steps will be common to many programs. The GraphPanel class and the createControlBar method take care of most of the rest of the tasks.

    // constructor for the window
    public Circles(int dx, int dy) {
	// call the JFrame constructor with the window title
	super("My Cool Circles");

	// set the program to terminate when the window is closed
	setDefaultCloseOperation(EXIT_ON_CLOSE);

	// create a drawing canvas and add it to the window
	GraphPanel mapCanvas = new GraphPanel(dx, dy);
	add(mapCanvas, BorderLayout.CENTER);

	// create the control bar
	JToolBar controlBar = createControlBar(mapCanvas);
	add(controlBar, BorderLayout.SOUTH);

	// done
    }

Next we're going to look at the private GraphPanel class. The GraphPanel class extends the JPanel class, which is a widget in which you can draw things like circles. We'll give the class one field, which is the number of circles to draw.

    private class GraphPanel extends JPanel {
	int numCircles;

The constructor for the GraphPanel class starts by calling the parent constructor and setting the initial number of circles to 10. Then it sets the size of the drawing area (dx, dy), the background color (white), and tells it to have an small border.

	// constructor for the graph panel
	public GraphPanel(int dx, int dy) {
	    super();

	    numCircles = 10;

	    // set its size, background color and border type
	    this.setPreferredSize(new Dimension(dx, dy));
	    this.setBackground(Color.white);
	    this.setBorder(BorderFactory.createEtchedBorder());
	}

Every widget that is inherits the JComponent class has a method called paintComponent. The paintComponent method is called any time that particular widget needs to be updated. All of the typical widgets in a window are children of the JComponent class, so all of them have a paintComponent method. Our GraphPanel is of type JPanel, whose parent is JComponent.

In order to draw the circles into our window, we need to override the paintComponent method. However, we still need to let the parent paintComponent method draw first, because there are housekeeping items that need to be executed before we draw anything. The method shown below does just that. First, it calls the parent's paint component method, then it calls a method that draws the circles.

	// function that gets called to refresh the screen
	public void paintComponent(Graphics g) {

	    // paint background
	    super.paintComponent(g);

	    // draw the vertices
	    drawCircles();
	}

The final piece is the drawCircles function. The function first gets the Graphics context. Think of the graphics context as the actual canvas on which things are drawn. It also has state information contained in it, such as the current pen color.

Most of the rest of the function is involved with picking where to draw the circles and how big to make them. Note that the GraphPanel object knows how large it is and we can use the getWidth() and getHeight() functions to access that information. Within the for loop, once the location and diameter of the circle has been chosen, the pen color is set to a random RGB value and the circle is drawn into the graphics context.

	public void drawCircles() {
	    Graphics g = this.getGraphics();
	    Random gen = new Random();

	    for(int i=0;i

The last piece is to look at the control bar, how it is created, and how we tell the GraphPanel object when to draw the circles and how many to draw.

The createControlBar method takes a GraphPanel as its argument because the action for the draw button needs to know how to communicate with it. The first thing the method does is create a JToolBar objects, which is a container object that can hold other widgets.

The first widget in the toolbar is a JLabel object that holds static text. This particular label tells the user what to put in the text field. After creating it, the label is added to the toolbar, making it the first element in the toolbar. The order in which items are added is the order in which they are drawn from left to right or top to bottom.

The second widget is a text field, which takes a default string and a size. The text field is added to the toolbar next, putting it next to the label.

If you want some space between elements in a toolbar, you have to create a JToolbar.Separator, which takes a width and height as arguments. We use this to put some space between the text field and the button.

The button is the final element of the toolbar except for a final separator object. The button is the only action item on the toolbar. The user expects something to occur when the button is clicked. Here is where we need a hook that tells the GraphPanel to update when the button is clicked.

    public JToolBar createControlBar(GraphPanel canvas) {

	// create a new toolbar object
	JToolBar toolbar = new JToolBar();

	// create a label field
	JLabel rootLabel = new JLabel("Number of Circles:");
	toolbar.add(rootLabel);

	// create an input field and add it to the bar
	JTextField rootid = new JTextField("10", 10);
	toolbar.add(rootid);

	toolbar.add(new JToolBar.Separator(new Dimension(60, 1)));

	// create a button and add it to the bar
	JButton runButton = new JButton(" Draw ");
	toolbar.add(runButton);

	// hook up a method to the button
	runButton.addActionListener(new RunListener(rootid, canvas) );

	toolbar.add(new JToolBar.Separator(new Dimension(30, 1)));

	return(toolbar);
    }

In order to create the hook, we create a private class that implements the interface for an ActionListener. A class that implements ActionListener must have a function called actionPerformed, that responds appropriately to an event.

In this case, the actionPerformed function does two things. First, it gets the number of circles to draw from the text field and puts that number into the GraphPanel's numCircles field. Second, it tells the graphics context of the GraphPanel to update itself. That forces the paintComponent method of GraphPanel to be executed, drawing the circles.

Because we need access to both the GraphPanel and the text field, the RunListener class has fields to hold them, and the constructor assigns those fields with the arguments passed into it.

    // private CircleListener hooks code with the button
    private class RunListener implements java.awt.event.ActionListener {
	GraphPanel canvas;
	JTextField field;
	
	public RunListener(JTextField tf, GraphPanel cf) {
	    field = tf;
	    canvas = cf;
	}

	public void actionPerformed( java.awt.event.ActionEvent evt ) {
	    // get the new number of circles from the text field
	    Integer numC = Integer.decode(field.getText());

	    canvas.numCircles = numC;

	    // tell the canvas to update to a new number of circles
	    Graphics g = canvas.getGraphics();
	    canvas.update(g);
	}
    }

That completes the Circles class. The important pieces to remember are the paintComponent method, which is where any widget draws itself, and the actionPerformed method, which is how a widget responds to an event.