Step 2 Swings Separable Model Architecture


Step 1 — Laying the Groundwork

For now, let’s just get a picture of the boy drawn in a window. We’ll also create some basic classes that won’t need to change as the program develops. These include a class to represent an article of clothing, Garment, and a class to handle the drawing of the boy and clothes, DressingBoard. We will write the class, MainFrame, which will contain the logic for assembling the interface and handling communication between components. MainFrame will evolve as the chapter progresses. Lastly, we will take advantage of a utility class for loading resources, ResourceUtils.

The Garment Class

The Garment class exists to encapsulate everything our program needs to know about an individual garment. Since a “garment” for our program’s purposes is really just an image drawn on the screen, there are only a few things that our program needs to know about each Garment: the image to be drawn, where on the screen to draw it and the name to display in the list. These properties will be immutable, meaning they can’t change. There is only one more property we would like to know about a Garment: is it currently being worn or not? This will determine whether or not to draw it. Due to the nature of our program, this is most definitely a mutable property. You might ask why “worn” should be a property of the Garment? Why not a property of the boy for instance? After all, isn’t it more natural to ask a boy if he’s wearing a shirt than to ask the shirt if it is being worn by a boy? Well, yes it is, but we won’t be dealing with a real boy or a real shirt – only abstractions of them. It will be more convenient to assign the “worn” property to the Garment for two reasons: 1) We would not otherwise need a Boy object and 2) every Garment is either worn or not, so there is a natural one-to-one correspondence. Example 14.1 gives the code for the Garment class.

Example 14.1: chap14.gui0.Garment.java

image from book
 1     package  chap14.gui0; 2 3     import  java.awt.Image; 4 5     public   class  Garment { 6 7       private  Image image; 8       private   int  x, y; 9       private   boolean  worn =  false ; 10      private  String name; 11 12      public  Garment(Image image,  int  x,  int  y, String name) { 13         this .image = image; 14         this .x = x; 15         this .y = y; 16         this .name = name; 17      } 18      public  String toString() { 19         return  name; 20      } 21      public  Image getImage() { 22         return  image; 23      } 24      public   void  setWorn(boolean  worn) { 25         this .worn = worn; 26      } 27      public   boolean  isWorn() { 28         return  worn; 29      } 30      public   int  getX() { 31         return  x; 32      } 33      public   int  getY() { 34         return  y; 35      } 36      public  String getName() { 37         return  name; 38      } 39     }
image from book

Garment is a simple class that acts as a container for data. It provides getters for all data (image, name, worn, x-and y- coordinates) and a setter for the mutable data (worn). It also overrides the Object.toString() method. Later on, you’ll see why we needed to override toString().

The DressingBoard Class

Unlike Garment, DressingBoard is more that just a container of data. It exists to execute the actual drawing of images onto the computer’s screen, and contains the logic for how to draw the boy and his clothes in a specified order. Every time the list of worn garments is changed by the user (through addition, deletion or reordering), Dressing-Board will need to draw the picture again. So, DressingBoard provides a method that accepts an array of Garments and repaints the boy’s image accordingly. Our program is not the only entity, however, that will tell the DressingBoard to repaint. The Swing event mechanism, in conjunction with the host operating system, also tells GUI components to repaint themselves. It is for this reason that even though DressingBoard is a simple class to define, we must first delve into the issue of painting in general.

Graphics and the AWT/Swing Framework

This section just touches on the subject of painting to the extent necessary to understand its use in this chapter’s program. It is beyond the current scope to discuss painting in great detail, but much can be learned by experimentation once the basics are understood.

Graphics (The “How” of Drawing)

The Java API offers sophisticated graphics abilities for drawing and painting, encapsulated in the java.awt.Graphics class and its descendant, the java.awt.Graphics2D class. These two classes provide tools to draw text, draw and fill shapes and areas of arbitrary complexity, manipulate and transform images, rotate, scale, shear, apply matrix transformations, etc. By the way, the terms “draw” and “paint” are practically synonymous. We will use them interchangeably.

The various methods of the Graphics and Graphics2D classes can be rough-sorted into three categories with the great majority falling into the first two categories:

  • Drawing operations that directly change pixel values

  • Property-related methods dealing with properties of the Graphics object itself

  • Miscellaneous methods

Drawing operation method names tend to begin with words such as “fill” and “draw”. Examples of drawing operations are “drawLine()”, “drawImage()”, “fillPolygon()”, “drawString()” and “fillArc()”. Some have different names such as “clearRect()” and “copyArea()”. In any case, you pass them coordinates, images, polygons, strings, etc. as the names suggest and the methods cause these objects to be drawn or filled or cleared or copied.

Property-related method names tend to begin with “set” and “get” such as “setColor()”, “getColor()”, “setFont()”, “getFont()”, “setPaint()”, “getPaint()”. They set and get various properties of the Graphics object and affect the results of subsequent drawing operations. They do not in themselves do any painting. Some property-setting methods have more active names like “shear()”, “translate()”, “rotate()” and “clip()” but, like their more modestly named cousins, they merely modulate the results of subsequent drawing operations.

Drawing operations and property-related methods are used in conjunction with each other. If, for example, one wanted to draw the string “Aliens” in a Graphics object named g at coordinate 50, 50 with a 48 point, blue sans-serif font, one would call two property-setting methods followed by a single drawing operation:

     g.setColor(Color.blue);     g.setFont(new Font("SansSerif", Font.PLAIN, 48);     g.drawString("Aliens", 50, 50);

The miscellaneous methods include methods that create Graphics objects (based on the current instance) and a couple helper methods named hit() and hitClip() that test for shape intersections. We won’t be using any of these miscellaneous methods in this chapter. Figure 14-2 illustrates the function of the Graphics object.

image from book
Figure 14-2: Graphics Drawing Operations and Property-Related Methods

The best way to learn how to use the many available Graphics methods is to obtain an instance of a Graphics object and start drawing. But, as you may have noticed if you’ve peeked at the javadocs, there are no public constructors for Graphics objects. So how does one get started?

The AWT/Swing Framework (The “When” of Drawing)

You will most likely never call a Graphics constructor directly since their constructors are protected. Besides, practically all their methods are abstract. The actual concrete Graphics instance used by the JVM is a platform-specific subclass of Graphics or Graphics2D). In order to obtain a reference to this concrete instance, it is necessary to begin with a reference to either a Component or an Image (java.awt.image). You may call the Image.getGraphics() or Component.getGraphics() methods to obtain the Graphics reference that actually draws into them. Components, as you know, are manifested in a visible way on the computer screen. One may draw directly onto the surface of any Component, immediately affecting that component’s screen pixels, by obtaining its Graphics and calling drawing operations on it. The Graphics that draws into a Component is known as an onscreen Graphics. An Image, on the other hand, is an object residing in memory and, like any other Java object, is not intrinsically viewable. One may draw directly into an Image by getting its Graphics and performing drawing operations on it, but one will only see the results by subsequently copying them into an onscreen Graphics object through one of Graphics’ image-copying methods. The Graphics object that draws into an Image is known as an offscreen Graphics, and the image into which it draws is referred to as an offscreen image.

Before we rush off and start blithely drawing into Components, there is more to say on the subject. Although it is possible at any time to grab a component’s Graphics and draw directly in it, you should know that a component’s pixels can and will be overwritten by many events not in your control. For instance, when another window comes in front of your lovingly painted component, the pixels that are covered by the window will change to look like the front window and they will need to be restored when the window moves away. The AWT/Swing framework automatically handles the repainting of components “damaged” by system and other events. It can handle cases such as when a window exposes a previously hidden component, as well as other detectable events such as when your application changes the text of a visible JLabel by calling JLabel.setText(). But the framework doesn’t automatically restore the results of custom painting, unless you allow it to manage your custom painting code for you. If you do, you’ll have to abide by its rules. But don’t worry, the framework makes it easy. Just give it the instructions for drawing your component, and it will handle the rest.

When to Paint into an AWT Component

For AWT components, you should override the component’s paint() method (see table 14-1) and place your custom drawing instructions inside the overridden method body. Component.paint() is called when a component needs to be repainted. You may perform whatever drawing operations you like (using the provided Graphics) and relax knowing that whenever your component’s pixels get messed up by events beyond your control, your painting instructions will again be executed.

Table 14-1: Component’s Painting Method

Method Name and Purpose

public void paint(Graphics g)

paints the component.

When to Paint into a Swing Component

Swing’s JComponents are Components but their paint methods have already been overridden to support the greater power and efficiency of the Swing architecture. For this reason, you are strongly discouraged from overriding the paint() method of a Swing component yourself. When it is time for a Swing component to paint itself, the paint() method for JComponent calls the following three methods in the order listed in table 14-2.

Table 14-2: JComponent’s Painting Methods

Method Name and Purpose

protected void paintComponent(Graphics g)

paints the component

protected void paintBorder(Graphics g)

paints the component’s border

protected void paintChildren(Graphics g)

paints the component’s children components

First, the paintComponent() method fills the component with its background color unless the component is transparent (see Component.setOpaque() and Component.isOpaque()). It then performs whatever custom painting is appropriate to the component. Next, the paintBorder() method paints whatever border the component may have (see JComponent.setBorder()). Finally, the paintChildren() method calls Component.paint() on all the component’s children. The method you should override when painting into a JComponent is paintComponent() which may be considered the Swing counterpart to an AWT component’s paint() method.

Swing components are double buffered by default which means that they have an offscreen Graphics as well as an onscreen Graphics associated with them. When a Swing component needs to be painted, the AWT/Swing framework performs all the necessary drawing into the offscreen graphics, and then copies the resulting image onto the component’s surface. This means that the Graphics object provided to the paintComponent(), paintBorder() and paintChildren() methods is not the actual Graphics that draws into the component. It is an offscreen Graphics, but go ahead and use it as if it were an onscreen Graphics. When Swing has executed your code, painted the component’s border and finally progressed through the hierarchy of children, it will copy the resulting offscreen image of the component to the screen. Although the use of an offscreen image introduces an additional step into the painting process, there are several benefits to it — including the fact that it is faster.

Calling Repaint

As previously explained, the paint() method of a component (AWT or Swing) is called when the AWT/Swing framework determines that a component needs to be repainted. It is also called when application code invokes a component’s repaint() method. (See tables 14-3 and 14-4). If at any time during your program’s execution you want to repaint a component, presumably because program-specific logic requires the component to display differently, then a call to any one of the repaint methods will cause the Swing painting mechanism to schedule the component to be painted. When it is ready, the framework will call Component.paint(). In the case of an AWT component, your code will be executed directly, or in the case of a Swing component, indirectly, when paint calls paintComponent(). You should never call the paint(), paintComponent(), paintBorder() or paintChildren() methods yourself as this could interfere with the framework’s automatic painting and cause undesirable results. If the entire component needs repainting, then call the repaint() method with no parameters. If, however, only a portion needs repainting, you may be able to make things more efficient by calling one of the repaint() methods that take parameters specifying the location and size of a rectangular area to repaint.

Table 14-3: Repaint Methods Defined by Component

Method Name and Purpose

public void repaint()

Schedules the component to be repainted.

public void repaint(int x, int y, int width, int height)

Schedules the specified rectangle within the component to be repainted.

Table 14-4: Repaint Methods Defined by JComponent

Method Name and Purpose

public void repaint(Rectangle r)

Schedules the specified rectangle within the component to be repainted.

Obtaining a Graphics2D

One more painting issue to consider is the use of the Graphics2D object. Graphics2D extends Graphics to provide an additional set of advanced graphics tools such as transparency, anti-aliasing, texture and gradients, as well as more sophisticated control over geometry, coordinate transformations, color management, and text layout. Although the API doesn’t explicitly state it, the Graphics object parameter passed to paint(), paintComponent(), paintBorder() and paintChildren() is an instance of Graphics2D. This has been true ever since version 1.2 of the Java platform, so it is safe to cast it to a Graphics2D inside the body of the paint methods as in:

     protected void paintComponent(Graphics g) {           Graphics2D g2 = (Graphics2D)g;     }

The DressingBoard Class (continued)

Now that we have discussed painting in the Swing framework, have a look at the code for DressingBoard. (See example 14.2). DressingBoard’s constructor loads and stores the image of the boy, calculates once and for all the image’s width and height and initializes its private array of Garments to a zero-length array. We give our Dressing-Board the method, setOrder(Garment[] garments) which updates its private array of Garments and triggers a call to paint() by calling repaint(). And of course, there is the all-important paintComponent() method which is overridden to paint the background white, do some arithmetic to find out where center is, and then draw the image of the boy followed by each garment in the Garment array (in order).

Example 14.2: chap14.gui0.DressingBoard.java

image from book
 1     package chap14.gui0; 2 3     import java.awt.Color; 4     import java.awt.Graphics; 5     import java.awt.Image; 6 7     import javax.swing.BorderFactory; 8     import javax.swing.JComponent; 9     import javax.swing.border.BevelBorder; 10 11    import utils.ResourceUtils; 12 13    public class DressingBoard extends JComponent { 14      private Image boyImage; 15      private Garment[] order; 16      private int boyWidth; 17      private int boyHeight; 18 19      public DressingBoard() { 20        setOpaque(true); 21        setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED)); 22 23        //image of completely clothed boy courtesy of www.hasslefreeclipart.com 24        boyImage = ResourceUtils.loadImage("chap14/images/boy.gif", this); 25 26        boyWidth = boyImage.getWidth(this); 27        boyHeight = boyImage.getHeight(this); 28        setOrder(new Garment[0]); 29      } 30      public void paintComponent(Graphics g) { 31        super.paintComponent(g); 32        int width = getWidth(); 33        int height = getHeight(); 34        g.setColor(Color.WHITE); 35        g.fillRect(0, 0, width, height); 36        int offsetX = (width - boyWidth) / 2; 37        int offsetY = (height - boyHeight) / 2; 38        g.drawImage(boyImage, offsetX, offsetY, this); 39        for (int i = 0; i < order.length; ++i) { 40          Garment garment = order[i]; 41          g.drawImage( 42            garment.getImage(), 43            garment.getX() + offsetX, 44            garment.getY() + offsetY, 45            this); 46        } 47      } 48      public void setOrder(Garment[] order) { 49        this.order = order; 50        repaint(); 51      } 52     }
image from book

Loading an Image or Resource

Thanks to the introduction of the javax.imageio package in Java 1.4, the loading of an image from a URL has become a simple and straightforward process. It wasn’t always this easy, as you may see if you look at the methods loadImage_pre_imageio and loadImage_pre_swing in lines 34 - 48 of example 14.3. On the other hand, constructing the correct URL can still be tricky and this is the reason for the ResourceUtils.relativePathtoUrl(String, Object) method (lines 15 - 19).

Example 14.3: utils.ResourceUtils.java

image from book
 1     package utils; 2 3     import java.awt.Component; 4     import java.awt.Image; 5     import java.awt.MediaTracker; 6     import java.awt.Toolkit; 7     import java.awt.image.BufferedImage; 8     import java.io.IOException; 9     import java.net.URL; 10 11    import javax.imageio.ImageIO; 12    import javax.swing.ImageIcon; 13 14    public class ResourceUtils { 15      public static URL relativePathToUrl(String path, Object obj) { 16        Class clazz = obj instanceof Class ? (Class)obj : obj.getClass(); 17        URL url = clazz.getClassLoader().getResource(path); 18        return url; 19      } 20      public static BufferedImage loadImage(String path, Object obj) { 21        return loadImage(relativePathToUrl(path, obj)); 22      } 23      public static BufferedImage loadImage(URL url) { 24        try { 25          return ImageIO.read(url); 26        } catch (IOException ignore) {} 27 28        return null; 29      } 30 31      /* 32       * For historical interest 33       */ 34      public static Image loadImage_pre_imageio(URL url) { 35        return new ImageIcon(url).getImage(); 36      } 37      public static Image loadImage_pre_swing(URL url) { 38        Component comp = new Component() {}; 39        MediaTracker tracker = new MediaTracker(comp); 40        Image image = Toolkit.getDefaultToolkit().createImage(url); 41        tracker.addImage(image, 0); 42        try { 43          tracker.waitForID(0); 44          return image; 45        } catch (InterruptedException ignore) {} 46 47        return null; 48      } 49     }
image from book

Ordinarily when it’s time to distribute your application, it is wise to package it into a single jar file that includes classes as well as other application resources such as images. Unfortunately, the URL for a resource is significantly different when it’s in a jar file than when it’s sitting in your computer’s file system. If your program code accesses a resource via a hard-coded URL, the program code will have a dependency on the distribution format, but if you specify resource paths that are relative to your application’s package structure, you can bundle all your application resources into a single jar file without having to change any code due to URL changes. The ResourceUtils.loadImage(String, Object) method allows you to specify class-relative paths from which it dynamically constructs the correct URL. In order for it to do this, it requires an object parameter. If the object is of type Class then it must be an application-defined class. If it is not of type Class, then it must be an instance of an application-defined class. Usually it’s most convenient to pass “this” as the Object parameter.

As a concrete example of usage, I keep all my generated class files and resources for this book in C:/JavaForArtists/classes. The images for this chapter are located in C:/JavaForArtists/classes/chap14/images. When I run chap14.gui5.MainFrame using the following command

     java -cp C:/JavaForArtists/classes chap14.gui5.MainFrame

the URL for the boy image is

     file:/C:/JavaForArtists/classes/chap14/images/boy.gif

But if I jar the contents of C:/JavaForArtists/classes/chap14 and C:/JavaForArtists/classes/utils into a file called Dressing.jar located in C:/JavaForArtists using the following command...

    jar -cMf C:/JavaForArtists/Dressing.jar -C C:/JavaForArtists/classes utils -C C:/JavaForArtists/classes chap14

and then run the program using

     java -cp C:/JavaForArtists/Dressing.jar chap14.gui5.MainFrame

the URL for the boy image is

     jar:file:/C:/JavaForArtists/Dressing.jar!/chap14/images/boy.gif

This is quite a bit different from the previous URL. Fortunately, by using the loadImage(String path, Object obj) method, all my code needs to specify for the location of the boy image is...

     chap14/images/boy.gif

...as in line 24 of image from book DressingBoard.java. The correct URL will be dynamically constructed.

The MainFrame Class

MainFrame is the entry point for our application. It constructs the main window. As this program evolves, MainFrame will also evolve. Each package will define its own MainFrame class.

Example 14.4: chap14.gui0.MainFrame.java

image from book
 1     package chap14.gui0; 2 3     import java.awt.BorderLayout; 4     import java.awt.Container; 5 6     import javax.swing.JFrame; 7 8     public class MainFrame extends JFrame { 9 10      private DressingBoard dressingBoard; 11 12      public MainFrame() { 13        setTitle(getClass().getName()); 14        setSize(600, 400); 15        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 16 17        Container contentPane = getContentPane(); 18        contentPane.setLayout(new BorderLayout()); 19        dressingBoard = new DressingBoard(); 20        contentPane.add("Center", dressingBoard); 21      } 22      public static void main(String[] arg) { 23        new MainFrame().setVisible(true); 24      } 25     }
image from book

Use the following commands to compile and execute the example. From the directory containing the src folder:

      javac –d classes -sourcepath src src/chap14/gui0/MainFrame.java      java –cp classes chap14.gui0.MainFrame

Run the program to see the image of a boy. Yikes! We need to put some clothes on him!

Quick Review

We created the Garment and DressingBoard classes which will not need further modification. We wrote MainFrame which we expect to evolve. We looked at various methods of loading an image and a convenience method for dynamically constructing a resource’s URL. We learned about the Graphics and Graphics2D classes. We learned how the AWT/Swing framework handles the painting of components and how to override the paint() method of an AWT component or the paintComponent() method of a Swing component to do custom painting.




Java For Artists(c) The Art, Philosophy, and Science of Object-Oriented Programming
Java For Artists: The Art, Philosophy, And Science Of Object-Oriented Programming
ISBN: 1932504052
EAN: 2147483647
Year: 2007
Pages: 452

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net