Java has extensive graphics capabilities. You can use these for
Much effort has been expended in improving Java graphics as this was
an area of much frustration in earlier releases. The basic graphics
package is something called the Abstract Window Toolkit (AWT). The
improved graphics sits on top of a library known as ``Swing''. Our
text makes frequent comparisons between the two. The legacy of AWT
still influences the way people go about learning and using Java
graphics.
Modern graphics and GUI's are a very successful application area of
object oriented programming and a study of them may help you feel more
comfortable with OO.
Graphics programs are somewhat unique and have patterns which you will
find unusual at first. Once you learn them, they are often repeated
and you can ignore the ``boiler plate''.
A very important aspect of GUI programming is event handling. We will
cover aspects of this this week.
Swing is part of the Java Foundation Classes (JFC). It is a
``non-peer-based'' GUI toolkit in which the user interface elements
(scrollbars, menus, text fields) are ``painted on'' to a uniform
background.
Java lets you change the ``look and feel'' of these GUI elements to be
similar to Mac, Windows and X-Motif. There is also a custom Java LAF
known as ``Metal''. (example: PlafTest.java)
Study the pattern of the following code which puts up a simple, titled frame:
/* example 7-1 of HC */ import javax.swing.*; public class SwingFrame { public static void main(String[] args) { HelloFrame frame = new HelloFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.show(); } } class HelloFrame extends JFrame { public HelloFrame() { setTitle("Hello"); setSize(WIDTH, HEIGHT); } public static final int WIDTH = 600; public static final int HEIGHT = 400; }
This draws a top level graphics window. It uses a JFrame class
which is a Swing extension of the AWT Frame class (Swing adds a J).
It is one of the few ``non-painted'' graphics components.
Frames are examples of containers and we will study ways of
inserting graphics objects into them.
Note the patterns in the sample code:
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);In other programs you would want to change this so that the program did not exit whenever the user closed a window. By default a frame is ``hidden'' when a user closes it but the program does not terminate.
Many of the useful JFrame methods are inherited from other
classes further up the hierarchy:
>From the Frame class the following methods are useful
Other methods are available from the Component and Window classes (particularly methods to resize and position the frame):
setLocation(x,y)This method can move the frame around. Note that y measures the number of pixels down from the top left corner of the screen. Also note that both arguments are integers.
setBounds(x, y, width, height)This allows you to define the position and size in one command.
Note that the coordinates in JFrame methods are with respect to
the whole screen. In other swing classes they are usually with
respect to a specific container.
It is possible to get the dimensions of the screen you are using by creating an object of class Toolkit and calling its getScreenSize() method. This returns the width and height as fields in a dimension object. The pattern is
Toolkit kit = Toolkit.getDefaultToolkit(); Dimension screenSize = kit.getScreenSize(); int screenHeight = screenSize.height; int screenWidth = screenSize.width;(where we note that the height and width are public variables!).
The example program CenteredFrame.java shows this in
action. Note that you can also read in your own icon for the image on
the top left of the window.
See pages 307-309 of HC for other API notes. Also see the main Java
API documentation (and remember to follow the links through to super
classes).
Although it is possible to draw directly onto a JFrame this is considered to be bad programming practice. The thing most swing programmers do is to add components to the content pane which is part of the JFrame. (See fig 7-7.) The thing to do is to call the getContentPane() method of your JFrame to create a Container object and then to add Components to this.
Container contentPane = getContentPane(); Component c = ...; contentPane.add(c);
Our hello world program adds an object which extends JPanel:
Container contentPane = getContentPane(); HelloPanel panel = new HelloPanel(); contentPane.add(panel);
What are panels? They are
Why do you extend JPanel? Because the basic panel is very boring. You need to override its paintComponent method so that the system will draw interesting objects.
g.drawString("Hello World!", MESSAGE_X, MESSAGE_Y);will draw a string at the specified position.
super.paintComponent(g);
Here is the complete program.
/* example 7-3 of HC */ import javax.swing.*; import java.awt.*; public class SwingHelloWorld { public static void main(String[] args) { HelloFrame frame = new HelloFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.show(); } } class HelloFrame extends JFrame { public HelloFrame() { setTitle("Hello"); setSize(WIDTH, HEIGHT); // add panel to frame HelloPanel panel = new HelloPanel(); Container contentPane = getContentPane(); contentPane.add(panel); } public static final int WIDTH = 600; public static final int HEIGHT = 400; } /** A panel that displays a message */ class HelloPanel extends JPanel { public void paintComponent(Graphics g) { super.paintComponent(g); g.drawString("Hello World!", MESSAGE_X, MESSAGE_Y); } public static final int MESSAGE_X = 75; public static final int MESSAGE_Y = 200; }
Although the previous example was a complicated form of saying
hello, we have now set the stage to actually draw pictures! All of the
action in our following examples will take place within the paintComponent method of our custom JPanel class.
Unfortunately, the old Graphics class has very limited methods
for drawing shapes (you could not vary line thicknesses or rotate
shapes amongst other shortcomings). Java now has a powerful Java2D library which you access by casting your Graphics
object to a Graphics2D object and then accessing its methods.
The Java2D library implements geometric shapes in an object oriented
fashion. There are classes to represent points, lines, rectangles and
ellipses and they build on one another in an object-oriented way. They
implement the Shape interface. In order to draw something, you
create such an object and then pass it to the draw method of
your Graphics2D object.
See figure 7-10 for an example of relationships between some shape classes.
Heavy weather
HC make heavy weather of the difference between float and double arguments of shape objects. We will step through some of these during lectures, but I suggest that you try to remember to call them with constants ending with a capital F. (Alternatively, use the Rectangle2D.Double type objects as they do.
Java2d has some great colours which you can set using the setPaint method. You select a colour, draw and then select a new colour.
c.brighter().brighter().brighter()(There is also a darker() method.)
frame.setBackground(SystemColor.window)sets the colour of the frame background to match other windows on your desktop. See others in Table 7-2.
For example, the following code fills in an ellipse and a rectangle.
/* Example 7.5 of Core Java Vol 1 */ import java.awt.*; import java.awt.geom.*; import javax.swing.*; public class FillTest { public static void main(String[] args) { FillFrame frame = new FillFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.show(); } } /* A frame that contains a panel with drawings */ class FillFrame extends JFrame { public FillFrame() { setTitle("FillTest"); setSize(WIDTH,HEIGHT); // add panel to frame FillPanel panel = new FillPanel(); Container contentPane = getContentPane(); contentPane.add(panel); } public static final int WIDTH = 400; public static final int HEIGHT = 400; } /* A panel that displays filled rectangles and ellipses */ class FillPanel extends JPanel { public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2 = (Graphics2D)g; // draw a rectangle double leftX = 100; double topY = 100; double width = 200; double height = 150; Rectangle2D rect = new Rectangle2D.Double(leftX, topY, width, height); g2.setPaint(Color.red); g2.fill(rect); // draw the enclosed ellipse Ellipse2D ellipse = new Ellipse2D.Double(); ellipse.setFrame(rect); g2.setPaint(new Color(0, 128, 128)); // dull blue green g2.fill(ellipse); } }
As well as having access to named fonts, Java allows you to specify logical font names which are always mapped to actual fonts which exist on the client machine.
You use fonts by creating a font object and calling setFont.
String message = "Hello World!"; Font f = new Font("Serif",Font.BOLD, 36); g2.setFont(f); g2.drawString(message, x, y);
Remember that in one of our early lab exercises we put effort into centering text. It is possible to very exactly center your text taking its actual font dimensions into account. This is the point of our last example. See Fig 7-13, and the discussion nearby, to understand the details.
/* Example 7.6 of Core Java Vol 1 */ import java.awt.*; import java.awt.font.*; import java.awt.geom.*; import javax.swing.*; public class FontTest { public static void main(String[] args) { FontFrame frame = new FontFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.show(); } } /* A frame with a text message panel */ class FontFrame extends JFrame { public FontFrame() { setTitle("FontTest"); setSize(WIDTH,HEIGHT); // add panel to frame FontPanel panel = new FontPanel(); Container contentPane = getContentPane(); contentPane.add(panel); } public static final int WIDTH = 400; public static final int HEIGHT = 400; } /* A panel that shows a centered message in a box. */ class FontPanel extends JPanel { public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2 = (Graphics2D)g; String message = "Hello World!"; Font f = new Font("Serif",Font.BOLD, 36); g2.setFont(f); // measure the size of the message FontRenderContext context = g2.getFontRenderContext(); Rectangle2D bounds = f.getStringBounds(message, context); // set (x,y) to top left corner of text double x = (getWidth()-bounds.getWidth())/2.; double y = (getHeight()-bounds.getHeight())/2.; // add ascent to y to reach baseline double ascent = -bounds.getY(); double baseY = y + ascent; // draw the message g2.drawString(message, (int)x, (int)baseY); // draw the baseline g2.setPaint(Color.gray); g2.draw(new Line2D.Double(x, baseY, x+ bounds.getWidth(), baseY)); //draw the enclosing rectangle Rectangle2D rect = new Rectangle2D.Double(x,y, bounds.getWidth(), bounds.getHeight()); g2.draw(rect); } }