Graphical User Interface Programming for Multi-Platform Applications in Java 1.1.
or
GUI Programming in Java for Everyone
Part 3 Reusable Parts
(Version 2)

Joseph Bergin, Pace University

Introduction

I thinking about my first two papers in this series, it struck me that I could do a better job with encapsulation and separation of duties. In particular, the userColorDialog object discussed in part 2 needed several parts, including a dialog box, some buttons which are also action listeners. I decided that the application would be cleaner and I would have an opportunity for reuse if I factored these parts out into another class. Actually this is just good design. Whenever something has several interacting parts relatively separate from the rest of the application, it should be factored out. This requires giving some thought to its interface with the rest of the application, of course. The original version of this paper used a slightly different event architecture.

The ColorDialog class

Factoring out all of the parts of the AllenApplet that relate to showing and handling the color dialog leads to the following class. To allow for the most reuse, this class should be public, so it is put into its own file named ColorDialog.java


import java.awt.*;
import java.awt.event.*;

public class ColorDialog extends Dialog
{	ColorDialog(Frame frame, Component component)
	{	super(frame, "Color Choice", false);
		this.component = component;
		setSize(200, 100);
		setResizable(false);
		Panel dialogPanel = new Panel(); // Layout the fields and labels
		add("Center", dialogPanel);
		dialogPanel.setLayout(new GridLayout(3, 2, 0, 2));
		dialogPanel.add(new Label("Red"));
		dialogPanel.add(red);
		red.setBackground(Color.red);
		dialogPanel.add(new Label("Green"));
		dialogPanel.add(green);
		green.setBackground(Color.green);
		dialogPanel.add(new Label("Blue"));
		dialogPanel.add(blue);	
		blue.setBackground(Color.blue);
		blue.setForeground(Color.white);
		Panel buttonPanel = new Panel(); // Layout the buttons.  
		add("South", buttonPanel);
		buttonPanel.add( new YesButton());
		buttonPanel.add( new NoButton());	
	}
	
	TextField red = new TextField(6);
	TextField green = new TextField(6);
	TextField blue = new TextField(6);
	Component component;
		
	class YesButton extends Button implements ActionListener 
	// Yes button for UserColor dialog.
	{	public YesButton()
		{	super("Do It");
			addActionListener(this);
		}
	
		public void actionPerformed(ActionEvent e)
		{	int r = (red.getText().length()==0)?0 : Integer.parseInt(red.getText());
			int g = (green.getText().length()==0) 
					? 	0 : Integer.parseInt(green.getText());
			int b = (blue.getText().length()==0)?0:Integer.parseInt(blue.getText());
			if(r<0) r = 0;
			if(g<0) g = 0;
			if(b<0) b = 0;
			component.setBackground
				(new Color(Math.min(r,255), Math.min(g, 255), Math.min(b,255)));
			component.repaint();
		}
	}

	class NoButton extends Button implements ActionListener 
	// No button for UserColor dialog.
	{	public NoButton()
		{	super("Cancel");
			addActionListener(this);
		}
	
		public void actionPerformed(ActionEvent e)
		{	setVisible(false);
		}
	}
	
}

The only difficulty in designing it was that in the earlier version it was clear from the context in which the code was written which component needed to have its color changed when the dialog's Do It button was clicked. Since we have separated functionality here, we need to make that explicit. Thus, when a new color dialog is created, the constructor is passed a reference to the component whose background is to be updated. We also need to specify a parent frame, as we do with all dialogs.

Then the creation of a ColorDialog object can be done with two lines as in:


		userColorDialog = new ColorDialog(myFrame, this);
		userColorDialog.setLocation(50, 50);

The dialog is then subsequently shown in the usual way with setVisible. Notice that the dialog itself, as well as the buttons/listeners for its component events are all part of this class. This control could be used in any application or applet in which the user was asked to select a background color for some component. A modification of it could also simply select a color, which is saved in an internal instance variable. Then supplying an instance method to retrieve that color could be used to obtain the color, which could be used for any purpose. In fact, this component could be made more general by permitting null to be passed as the component to be updated. In this case the dialog would not attempt to set the color of an item, but would simply save it for later retrieval by a getCurrentColor method. These refinements have been incorporated into the next version.

The ColorSliderDialog class

While I was working on this, I noticed in the third edition of Core Java (Core Java 1.1, Volume 1, Horstmann, and Cornell, Prentice-Hall, 1997), a nice control that set the RGB color of an item by using sliders, actually ScrollBars, to select the color values. I took the ideas there and created the following ColorSliderDialog, that can be used to ask the user to select the color of any component. Here is an example of how it looks embedded in an updated AllenApplet. Instead of typing numbers, the user just slides the controls. The numeric values in the labels are continuously updated as the sliders are moved and the small color patch at the right edge of the dialog updates to show the currently selected color. The Do It button is then used to set the background of its component, here the applet, to the selected color.


import java.awt.*;
import java.awt.event.*;

public class ColorSliderDialog extends Dialog
{	/** Create a new color slider dialog.  The dialog will accept an rgb color from
	*	the user and when the "Do It" button is pressed the color of the component named
	*	in the initialization will be changed to this new color.
	*/
	public ColorSliderDialog(Frame frame, Component component)
	{	super(frame, "Color Choice", false);
		this.component = component;
		setSize(225, 100);
		setResizable(false);
		add("Center", dialogPanel);
		dialogPanel.setLayout(new GridLayout(3, 2, 0, 2));
		dialogPanel.add(redLabel);
		dialogPanel.add(red);
		red.setValues(0, 0, 0, 255);
		dialogPanel.add(greenLabel);
		dialogPanel.add(green);
		green.setValues(0, 0, 0, 255);
		dialogPanel.add(blueLabel);
		dialogPanel.add(blue);	
		blue.setValues(0, 0, 0, 255);
		Panel buttonPanel = new Panel(); // Layout the buttons.  
		add("South", buttonPanel);
		buttonPanel.add( new YesButton());
		buttonPanel.add( new NoButton());	
		add("East", colorPanel);
		colorPanel.setBackground(Color.black);
	}
	
	public Color getCurrentColor()
	{	return currentColor;
	}
	
	ColorPanel colorPanel = new ColorPanel();
	Panel dialogPanel = new Panel(); // Layout the fields and labels
	Scrollbar red = new ColorScroller();
	Scrollbar green = new ColorScroller();
	Scrollbar blue = new ColorScroller();
	Label redLabel = new Label("Red 0");
	Label greenLabel = new Label ("Green 0");
	Label blueLabel = new Label ("Blue 0");
	Component component;
	Color currentColor = Color.black;
		
	private class ColorPanel extends Panel
	{	public Dimension getMinimumSize()
		{	return new Dimension(25, 50);
		}
	}

	private class YesButton extends Button implements ActionListener 
	// Yes button for UserColor dialog.
	{	public YesButton()
		{	super( "Do It");
			addActionListener(this);
		}
		
		public void actionPerformed(ActionEvent e)
		{	int r = red.getValue();
			int g = green.getValue();
			int b = blue.getValue();
			redLabel.setText("Red " + r);
			greenLabel.setText("Green " + g);
			blueLabel.setText("Blue " + b);
			currentColor = new Color(r, g, b);
			if(component != null)
			{	component.setBackground(currentColor);
				component.repaint();
			}
		}
	}

	private class NoButton extends Button implements ActionListener 
	// No button for UserColor dialog.
	{	public NoButton()
		{	super( "Cancel");
			addActionListener(this);
		}
		
		public void actionPerformed(ActionEvent e)
		{	setVisible(false);
		}
	}
	
	private class ColorScroller extends Scrollbar implements AdjustmentListener
	{	public ColorScroller()
		{	super(Scrollbar.HORIZONTAL);
			addAdjustmentListener(this);
		}
	
		public void adjustmentValueChanged(AdjustmentEvent e)
		{	int r = red.getValue();
			int g = green.getValue();
			int b = blue.getValue();
			redLabel.setText("Red " + r);
			greenLabel.setText("Green " + g);
			blueLabel.setText("Blue " + b);
			colorPanel.setBackground(new Color(r, g, b));
			colorPanel.repaint();
		}
	}
	
}

Here we have simply replaced the text fields red, green, and blue with ScrollBar objects. Scroll bars register AdjustmentListeners and generate AdjustmentEvents. When the user slides the control, the adjustmentValueChanged message is sent to its listeners. So, in addition to the ActionListeners for the buttons we have our new ColorScroller class implement ScrollListener to listen for these events. Note that each of the three scroll bars acts as its own listener.

When you create a scroll bar you say whether its orientation is horizontal as here, or vertical. You also need to give it four additional integer parameters, either in the constructor, or later in the setValues message. The first parameter gives the initial value of the scroll bar and the last two give the range of values that it returns as it is slid from left to right. The second parameter is the size of the region that the scroll bar is scrolling or making visible. Here we aren't scrolling a visible region at all, so we use zero. Since we use zero for the initial value of all scroll bars that is equivalent to RGB black. We then use getValue to retrieve the new value that the user sets.

We have also provided a getCurrentColor method in this class and made the action routines aware that null might be sent for the component to be updated. This control is then quite useful in any GUI program in which the user must be asked to choose a color. The user even gets feedback as to the chosen color.

Notice how we use private inner classes for encapsulation here. These components/listeners have no purpose in the wider application or applet than to work with this dialog box. We therefore hide them inside the dialog box itself. This is one of the best software engineering features that was added to Java 1.1. Too bad that Microsoft seems reluctant to implement inner classes.

There are circumstances in which the listeners should not be the same as the classes of the controls that they listen to. This is when you are trying to build a general control that can be used to control different kinds of things. An AWT Button is such a control. If the listener is embedded within the class of such a control then the only way to get different behavior is to build a subclass of the control. This is not always desirable, as in the case of the button. In such a case, we want the user to be able to attach a special listener (or several) to the control to achieve the specialized behavior. In the ColorSliderDialog, however, we always want the same behavior, so it is appropriate to put the listeners inside as inner classes.

In more general circumstances, however, it is sometimes desirable to put a listener inside of the control class, but provide for the user to supply an alternate listener when appropriate. This can be done by making the listener an instance variable of the control class and providing one or more constructors for that class that supply substitute listeners. The details are discussed nicely in the second edition of Graphic Java.

Presenting Information in your Java Applet or Application

In the earlier parts of this series of papers, when we have drawn something into our applet or application, it has disappeared when the containing frame is repainted for any reason. Now we would like to see how to present information and have it remain when the window is resized, covered and then uncovered, or repainted for another reason. If your information is just text, you can do this easily by just using Labels, TexFields, and TextAreas. These components take care of repainting themselves and their contents as necessary. The problem comes if you want to do something graphical.

Java.awt.component has two methods that help us here. These are paint and repaint. Normally repaint is never overridden in new classes, but it is fairly frequently called. It is also called by the infrastructure when a component needs to be updated for any reason. On the other hand, paint is almost never called from code you write, but it is often overridden. The prototypes for these methods are


public void repaint();
public void paint(Graphics g);

We are going to look at a simple applet which might be the early stages of the design of an object modeling applet or application. The general idea is that we want to draw labeled figures and connect them with lines. The figures represent classes in an application and the lines represent relationships between the classes, such as the inheritance relationship. Below, we show a very simple example of the kind of thing we want to do. The code that produces this is not very sophisticated and we shall want to make a few modifications in the sequel.

The key to this is the paint method of the Canvas object that we have placed into the applet. A canvas is just a drawing component. The painting process starts at the applet which sees to calling the repaint method of each of its parts as necessary. The applet is painted by painting each of its components. Therefore, we have overridden the paint method of the Canvas class so that what we draw onto the canvas won't disappear the next time the canvas is repainted. To see the real effect of this you need to run the applet and then cover its window with another window and then see that when it is uncovered again the window is refreshed. This will also happen if you resize the window. Note that we don't call the paint method either directly or indirectly (by calling repaint). The system takes care of that for us.


import java.awt.*;
import java.applet.Applet;

public class PaintApplet extends Applet
{
	public void init() 
	{	Canvas drawArea = new TestCanvas();
		add (drawArea);
		drawArea.setBackground(Color.white);	
	}
	
	public Dimension getPreferredSize()
	{	return new Dimension(400, 300);
	}
	
	class TestCanvas extends Canvas
	{	public Dimension getPreferredSize()
		{	return new Dimension(400, 300);
		}
 
		public void paint( Graphics g ) 
		{	g.drawLine(55, 75, 225, 75);

			g.drawRect(30, 50, 50, 50 );
			g.drawRect(200, 50, 50, 50 );
			g.setColor(Color.white);
			g.fillRect(31, 51, 49, 49);
			g.fillRect(201, 51, 49, 49);
			
			g.setColor(Color.red);
			g.drawString("A", 55, 75);
			g.drawString("B", 225, 75);
		}
		
	}

}

There are a few important points to make about this simple applet. First, the getPreferredSize method of the TestCanvas class is essential. Without it, the canvas will be drawn so small (zero by zero) as to be invisible. All we would see this the gray background of the applet itself. This is often forgotten.

The other point is the need for the two calls to fillRect within the paint method. Without them only the outlines of the rectangles are drawn which means that the line, which was drawn from the center of the rectangle, would be visible "beneath" the rectangle. Like this:

Note that we draw the filled rectangle one pixel over, one pixel down and one pixel smaller in each dimension than the rectangle itself. This avoids painting over the boundary line, but covers all of the inside.

Working With Graphical Figures

The method that we used to draw the figures in the above applet is not very flexible and is quite error prone. The centers of the rectangles were computed by hand. I didn't make any errors this time when I first tried it, but I often do. Getting the bounds of the filled rectangle aligned with that of the outer boundary rectangle is tedious and error prone. If we wanted to move one of the rectangles we would have a bit of recomputation to do and the editing would again be open to errors. We would be much better off if we encapsulate the figures to be drawn as objects themselves.

Therefore we want a RectangleFigure class that represents a drawable, movable, labeled rectangle. The Rectangle class of java.awt is not adequate for this. It doesn't even know about drawing. It is the graphics class that draws such rectangles. We want the RectangleFigure classes to take over this task for us. For our purposes here we want them to be filled rectangles with boundaries and we want to display a label as above. Below we see such a class. It could have extended Rectangle. The reason for not doing so will become clear in a bit.


	public class RectangleFigure implements java.io.Serializable
	{	private Rectangle bounds;
		private String label;
		
		public RectangleFigure(String label, int x, int y, int w, int h)
		{	bounds = new Rectangle(x, y, w, h);
			this.label = label;
		}
		
		public void draw(Graphics g)
		{	int x = bounds.x, y = bounds.y, h = bounds.height, w = bounds.width;
			g.drawRect(x, y, w, h);
			Color c = g.getColor();
			g.setColor(Color.white);
			g.fillRect(x+1, y+1, w - 1, h - 1);
			g.setColor(Color.red);
			g.drawString(label, x + w/2, y + h/2);
			g.setColor(c);
		}
		
		public Rectangle rectangle(){ return bounds; }

		public Point location(){ return new Point(bounds.x, bounds.y); }

		public Point center()
		{	return new Point(bounds.x+bounds.width/2, bounds.y+bounds.height/2); 
		}

		public Dimension dimension()
		{	return new Dimension(bounds.width, bounds.height);
		}
		
		public void move(int x, int y)
		{	bounds.x = x; bounds.y = y;
		}
		
		public void drawConnect(RectangleFigure b, Graphics g)
		{	g.drawLine(center().x, center().y, b.center().x, b.center().y);
		}
	}

The interesting methods here are draw and drawConnect. Method draw draws the rectangle in the graphics current color, then changes the color to white and fills the interior. Before doing so it obtains the current color since we want to set the color back to this value at the end. If we don't then all other uses of this graphics object will result in coloring with the color that we set last. Note that the arguments to the draw routines that we call from within paint, being symbolic, are easier to understand and verify than those of our original version.

Also note, and it is very important, that we have a number of getter and setter methods for this class. Method rectangle, location, center, and dimension are all getter methods. Method move is a setter method. Note that these are much more than just getting and setting corresponding instance variables, however. We have established here a more logical interface, independent of the actual instance variables used in the implementation.

Method drawConnect just draws a line from the center of the "this" figure to the center of the parameter figure. Note that we need to call drawConnect to draw the line between the boxes before we draw the boxes themselves, so that the hidden parts of the line will be covered up.

I made the RectangleFigure class implement the Serializable interface. This permits these objects to be stored in files and transmitted over the net using the object serialization API.

We could use this class in the following applet to achieve exactly the same effect that we saw originally. Notice especially how much simpler the paint method is than in the original version.


import java.awt.*;
import java.applet.Applet;
import java.awt.event.*;

public class PaintDrawApplet extends Applet
{	Canvas drawArea = new TestCanvas();
	
	public void init() 
	{	add (drawArea);
		drawArea.setBackground(Color.white);
	}
	
	RectangleFigure a = new RectangleFigure("A", 30, 50, 50, 50);
	RectangleFigure b = new RectangleFigure("B", 200, 50, 50, 50);
	
	public Dimension getPreferredSize()
	{	return new Dimension(500, 300);
	}
	
	class TestCanvas extends Canvas
	{	public Dimension getPreferredSize()
		{	return new Dimension(400, 300);
		}
 
		public void paint( Graphics g ) 
		{	a.drawConnect(b, g);
			a.draw(g);
			b.draw(g);
		}		
	}
}

This doesn't show off our new flexibility, however. We can also move these new figures. A simple extension of the above applet will use a button to move one of the two RectangleFigures. As usual, we just add a button and make it a listener that will do what we want when the button is clicked.


import java.awt.*;
import java.applet.Applet;
import java.awt.event.*;

public class PaintDrawApplet extends Applet
{	Canvas drawArea = new TestCanvas();
	
	public void init() 
	{	add (drawArea);
		drawArea.setBackground(Color.white);
		add(moveButton);

	}
	
	RectangleFigure a = new RectangleFigure("A", 30, 50, 50, 50);
	RectangleFigure b = new RectangleFigure("B", 200, 50, 50, 50);
	Button moveButton = new MoveButton();
	
	public Dimension getPreferredSize()
	{	return new Dimension(500, 300);
	}
	
	class TestCanvas extends Canvas
	{	public Dimension getPreferredSize()
		{	return new Dimension(400, 300);
		}
 
		public void paint( Graphics g ) 
		{	a.drawConnect(b, g);
			a.draw(g);
			b.draw(g);
		}
		
	}
	
	class MoveButton extends Button implements ActionListener
	{	public MoveButton()
		{	super("Move");
			addActionListener(this);
		}
		
		public void actionPerformed(ActionEvent e)
		{	b.move(b.location().x, b.location().y + 20);
			drawArea.repaint();
		}
	}

}

All we have added here is the action that moves the "b" figure 20 pixels vertically. Not very interesting, but it shows the basic technique. Note that we do need to call repaint here to get the new figure shown. If we hadn't done this we would not have seen any effect until the window needs to be repainted for some other reason.

After pushing the Move button a few times we get the figure shown below.

Improving our Figure Applet

There are a number of improvements that we could make to this. Feel free to implement as many as you like. The first and simplest is to center the label in the figure. What we see here is that the lower left corner of the label is at the center of the rectangle. It should really be the center of the label. The FontMetrics class can be used to give the height and length of a string.

Next we could be more flexible about the drawing and filling colors. The RectangleFigure class could be given new instance variables and means of getting and setting the colors to be used. We could set these in constructors, of course, but it is advantageous to permit a bit more flexibility.

We have used fixed sizes here. This means that either the labels must be kept short, or they will run outside the boxes. In some applications it might be a good thing to let the box resize itself if it has a long label. You probably want to maintain a minimum size, however. We could also provide a getter and a setter for the label.

A much more extensive change, however, comes from rethinking what kind of things we want to draw on the screen. Most likely, if we are trying to build an object modeling tool, we want to draw more than rectangles and lines. We might want ovals and circles, as well as more general polygons. They should be connectable with lines and should generally behave like our rectangles do.

To do this requires that we build a hierarchy of drawable figures. Perhaps we should start with a class named Figure that has much of the functionality of our rectangle class and probably all of its instance variables as well. It most likely is an abstract class and implements its two draw methods as no-ops (methods that do nothing), or leave them unimplemented.

Then RectangleFigure could extend Figure and implement the draw methods as here. We could then have additional classes that also extend Figure. Note that most of these classes will also want the bounds rectangle, even if their visual representation is not rectangular. We might also need one or more Line classes to implement various kinds of connections between the other Figures.

With this change, we will be inheriting a number of methods, such as move and location, that behave the same in all figures. In the future, a change to this superclass (Figure) will have its effects seen in all of its subclasses automatically.

Once we have the added flexibility of a hierarchy of Figures, we might want to make the painting easier. One way to do this is to give the applet a jave.util.Vector, in which to hold information about the Figures to be drawn. We "create" a drawing by putting Figure objects into this "draw vector". The paint method of the applet (or here, canvas), just draws everything in the Vector. Note that the fact that all the objects in the vector are subclasses of a class with a draw method is critical to making this work. So inheritance helps us here.

If we add a draw vector, we need to think again about the connections between the various figures. We don't want to put lines into the draw vector since the boxes that they connect must be movable. When we move a box, we need to redraw the line that connects it to other boxes, but these lines have moved as well. We need a way to directly represent the connection between objects, beyond the fact that we represent this as a line.

One way to do this successfully, is to have a Figure called a Connection. It has two Figure references as instance variables. When we draw a connection we draw a line between the centers of these two Figures. ( If one or both of these figures can be lines themselves, we need to be a bit more sophisticated, though.) The advantage of this scheme is that we can put these objects into the draw vector along with the other objects. Note that we need to draw all of the connections first, however.

Once we have Connection objects, the drawConnect method that we have used above becomes a method to return a new Connection object for the two Figures in question instead of a drawing method to draw the line directly.

Finally, and most ambitious, is a means of allowing the user to "grab" and move the figures directly using the mouse. To do this a method called rubberbanding is used. A very good source to learn about rubberbanding is the book Graphic Java, by David Geary (Prentice-Hall, 1997). Be sure to get the second edition. He shows how rubberbanding can be used to draw objects, but the same techniques can be used to move and resize them as well.

Now that I have given you all of these skills, you can do something for me. An assignment, actually. Learn about the Unified Modeling Language (UML), which is a graphical technique for designing object oriented software. Build me (in Java) a simple UML modeling tool. Send me a copy when you've finished.

One more thing. If you create nice object modeling diagrams as suggested above, you will certainly want to save them in a file and print them out. More about printing next.

Printing From a Java Application

If you have presented some useful information in an application, your user may want to print it. Printing from an Applet is problematical, since an unfriendly applet downloaded off of the net could take over your printer. Therefore we must use an application if we want to print. We will modify the above applet to allow printing by first providing a frame in which to run the applet and then turning the applet into an application, Just as we did with the AllenApp in the first part of this tutorial. In fact the same class AllenFrame.java can be modified by just changing a few names.

We also add a new constructor to the PaintDrawApplet so that we can get and remember the Frame in which the applet code is running. We also did this with the AllenApp.


	Frame myFrame;
	
	public PaintDrawApplet(Frame f){myFrame = f; }

Next we are going to add a new button labeled "Print" to cause the contents of the drawArea to be printed. We do this in init, of course. We also add a new actionPerformed method that will cause the Applet's new print method to be called.


	class PrintButton extends Button implements ActionListener
	{	public PrintButton()
		{	super("Print");
			addActionListener(this);
		}
		
		public void actionPerformed(ActionEvent e)
		{	PaintDrawApplet.this.print();
		}
	}

The print method of the applet just calls the print method of the canvas. All of the interesting things happen within this new method of the TestCanvas class.

The key to printing is the paint method that we have already seen. We don't need to modify it however. All we need to do is see that it is called with a special parameter: a graphics object that knows how to print. This can be obtained from the toolkit of the application. We saw before that we can get a toolkit for processing images. The same is used for printing.

First we ask the toolkit for PrintJob, passing it a (non null) frame, a title for the job, and additional, system dependent parameters. Usually you just pass null for this last argument, preserving cross platform portability, but giving up some finer control of your printer. This call to getPrintJob causes the standard system print dialog to appear so that you can select a destination and number of copies and the like.

If the user cancels this print dialog, the PrintJob returned will be null. Therefore this needs to be checked for.


		public void print()
		{	PrintJob printing = myToolkit.getPrintJob(myFrame, "Testing", null);
			if(printing == null)
				System.out.println("No print job");
			else
			{	Graphics drawer = printing.getGraphics();
				if(drawer == null) 
					System.out.println("No Graphics.");
				else
				{	paint(drawer);
					drawer.dispose();
				}
				printing.end();
			}
		}

Then for each page to be printed (here just one) we ask that PrintJob for its graphics object, by calling its getGraphics method. We then just call our ordinary paint method, passing this graphics context as a parameter. Then we should dispose of the graphics object as usual. If there are several pages to be printed you do this three step process in a loop: getGraphics, paint, dispose. Finally when you are all done printing call the end method of the PrintJob. That's all there is to it. There are some refinements, however, as you can discover from the usual reference material.

To conclude, here is the complete Applet, though it is missing the Frame class that we use to contain it. If you try to run this version as an applet, you will get a security violation, however. The RectangleFigure class is included in the same file, but in reality it should be separated out.


import java.awt.*;
import java.applet.Applet;
import java.awt.event.*;

public class PaintDrawApplet extends Applet
{	TestCanvas drawArea = new TestCanvas();
	Toolkit myToolkit = Toolkit.getDefaultToolkit();
	Frame myFrame;
	
	public PaintDrawApplet(Frame f){myFrame = f; }
	
	public void init() 
	{	add (drawArea);
		drawArea.setBackground(Color.white);
		add(moveButton);
		add(printButton);
	}
	
	RectangleFigure a = new RectangleFigure("A", 30, 50, 50, 50);
	RectangleFigure b = new RectangleFigure("B", 200, 50, 50, 50);
	Button moveButton = new MoveButton();
	Button printButton = new PrintButton();
	
	public void print(){drawArea.print();}
	
	public Dimension getPreferredSize()
	{	return new Dimension(500, 300);
	}
	
	class TestCanvas extends Canvas
	{	public Dimension getPreferredSize()
		{	return new Dimension(400, 300);
		}
 
		public void paint( Graphics g ) 
		{	a.drawConnect(b, g);
			a.draw(g);
			b.draw(g);
		}
		
		public void print()
		{	PrintJob printing = myToolkit.getPrintJob(myFrame, "Testing", null);
			if(printing == null)
				System.out.println("No print job");
			else
			{	Graphics drawer = printing.getGraphics();
				if(drawer == null) 
					System.out.println("No Graphics.");
				else
				{	paint(drawer);
					drawer.dispose();
				}
				printing.end();
			}
		}

	}
	
	class MoveButton extends Button implements ActionListener
	{	public MoveButton()
		{	super("Move");
			addActionListener(this);
		}
		
		public void actionPerformed(ActionEvent e)
		{	b.move(b.location().x, b.location().y + 20);
			drawArea.repaint();
		}
	}

	class PrintButton extends Button implements ActionListener
	{	public PrintButton()
		{	super("Print");
			addActionListener(this);
		}
		
		public void actionPerformed(ActionEvent e)
		{	print();
		}
	}
}

	class RectangleFigure implements java.io.Serializable
	{	private Rectangle bounds;
		private String label;
		
		public RectangleFigure(String label, int x, int y, int w, int h)
		{	bounds = new Rectangle(x, y, w, h);
			this.label = label;
		}
		
		public void draw(Graphics g)
		{	int x = bounds.x, y = bounds.y, h = bounds.height, w = bounds.width;
			g.drawRect(x, y, w, h);
			Color c = g.getColor();
			g.setColor(Color.white);
			g.fillRect(x+1, y+1, w - 1, h - 1);
			g.setColor(Color.red);
			g.drawString(label, x + w/2, y + h/2);
			g.setColor(c);
		}
		
		public Rectangle rectangle(){ return bounds; }

		public Point location(){ return new Point(bounds.x, bounds.y); }

		public Point center()
		{ return new Point(bounds.x+bounds.width/2, bounds.y+bounds.height/2); 
		}

		public Dimension dimension()
		{ return new Dimension(bounds.width, bounds.height);
		}
		
		public void move(int x, int y)
		{	bounds.x = x; bounds.y = y;
		}
		
		public void drawConnect(RectangleFigure b, Graphics g)
		{	g.drawLine(center().x, center().y, b.center().x, b.center().y);
		}
	}
	


You can get all of the files for this applet/application from the part 3 files directory on sol.

That is all. Enjoy.