Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
http://java.sun.com/products/jfc/tsc/articles/fill-article1/ Article Painting with Fill Objects Painting with Fill Objects At the 2001 JavaOne Conference, we gave a talk called Rich Clients for Web Services (TS-2734) that featured an eye-catching user interface for the iPlanet Calendar Server. You can see a snapshot of the GUI to the left; more pictures are in the java.sun.com article about the presentation. Later we expanded upon the demo for 2002 JavaOne, which you can read about here. This is the first in a series of articles that describe the techniques we used to create the calendar demo. In this article we tell you how to share painting code, and we give you some tools to help you create enticing user interfaces. First, we describe how painting works in Swing components (which, for this article, we'll define as components that inherit from the JComponent class). Then we present a base class, Fill, that encapsulates the task of painting a rectangular area. Next, we show some increasingly complex subclasses of Fill, including a fill that paints a background image, another that tiles a fill (such as an image), and one that paints an image-based border. Finally, we give you a mouse-based tool to help you specify the images to use in the image-based border. How Painting Occurs in Swing Components Swing components are painted by four tightly coupled methods: paint, paintChildren, paintBorder, and paintComponent. The paint method is called automatically. It, in turn, calls the paintComponent method to paint the component itself, paintBorder to paint a border in the component's insets area, and then paintChildren to recursively paint the component's descendants. This is explained in more detail in Painting, a section in The Java Tutorial. You can change the way a Swing component's border is painted by setting its border property. The value of this property is a simple delegate object (a Border) whose paintBorder method paints the area defined by the component's insets. You can customize some of the painting performed by paintComponent by changing the values of component properties such as foreground, background, and font. Unlike for borders, however, you can't provide a paintComponent delegate. One reason for this is that paintComponent methods tend to be highly dependent on their class -- a paintComponent method for one class usually can not be easily shared with other classes. You can, however, override paintComponent in a subclass. To avoid unnecessarily duplicating code, you can factor some common painting tasks into reusable classes. The Base Class: Fill The examples in this article use a simple base class called Fill that can be used to create reusable, combinable objects that paint a rectangular area. The Fill class is similar to Border -- it's really just an encapsulation of paint methods: public class Fill { public void paintFill(Component c, Graphics g, Rectangle r) {...} public void paintFill(Component c, Graphics g) {...} public void paintFill(Component c, Graphics g, int x, ...) {...} } The first paintFill method just paints within the area defined by the rectangle r using the Graphics object g. Typically the component parameter, c, is the target of the Graphics object although it's not required to be. The other methods are convenience methods. The second one computes the coordinates of the component's insets rectangle and then passes them to the first method. The third one creates a Rectangle based on the parameters and passes it to the first method. What the paintFill method paints can reflect the values of the specified component's properties. The default implementation of the paintFill method does this -- it just fills the specified rectangle with the component's background color. To use a Fill object to paint a Swing component, you just create a subclass of the component's class and override the paintComponent method like this: public void paintComponent(Graphics g) { Graphics gFill = g.create(); myFillObject.paintFill(this, gFill); gFill.dispose(); } Note that we've passed a copy of the Graphics object to the paintFill method. This insulates any other painting code we might add to the paintComponent method from changes made to the Graphics object by the paintFill method. You can find the complete source code for the Fill class in Fill.java. To get all the source files for this article's examples, download border.zip. Example: CrossFill Here's an example of a Fill subclass that paints a cross in its rectangular area: public class CrossFill extends Fill { ... private void configureGraphics(...) {...} public void paintFill(Component c, Graphics g, Rectangle r) { int x = r.x, y = r.y, w = r.width, h = r.height; configureGraphics(c, g, x, y, w, h); g.drawLine(x, y, x+w, y+h); g.drawLine(x+w, y, x, y+h); } } As you can see, the paintFill method just paints two diagonal lines between the corners of the rectangle. The configureGraphics method, which we omitted for brevity, sets properties of the Graphics object to paint a wide line that's filled with a gradient. Here's a screenshot of an application, called TestCrossFill, that creates a BorderLayout of five JPanels whose paintComponent methods paint using a shared instance of the CrossFill class. You can find the code for this example in TestCrossFill.java and CrossFill.java. You can run the example by downloading the examples JAR file and then running TestCrossFill, using J2SE v 1.4, like this: java -cp examples.jar TestCrossFill Example: ImageFill A more practical example of a Fill subclass is ImageFill, which paints a single BufferedImage. ImageFill's paintFill method scales the BufferedImage to match the width and height arguments. Since image scaling is expensive relative to painting, we cache a small number of the most recently used scaled copies of the original image. Caching scaled copies of the images will be a big benefit later on, when we apply the ImageFill class to painting borders. To create an instance of an ImageFill object we need to create a BufferedImage. This can be done quite simply with the new (in JDK 1.4) ImageIO class: BufferedImage image = ImageIO.read("background.jpg"); imageFill = new ImageFill(image); The static ImageIO.read method reads the image file and creates the BufferedImage synchronously. There's no need for a MediaTracker or ImageObserver. The paintFill method for our ImageFill class looks like this: public void paintFill(Component c, Graphics g, Rectangle r) { if ((r.width > 0) && (r.height > 0)) { BufferedImage fillImage = getFillImage(c, r.width, r.height); g.drawImage(fillImage, r.x, r.y, c); } } The private method getFillImage takes care of scaling and caching. The screenshot below shows the TestImageFill demo application in action. Each panel is an instance of the TestImageFill class, which paints its interior with a single shared ImageFill object. To create new panels, just drag out a bounding rectangle with the mouse. You can find the code for this example in TestImageFill.java and ImageFill.java. (You also need Fill.java.) You can run the example by downloading the examples JAR file and then running TestImageFill, using J2SE v 1.4, like this: java -cp examples.jar TestImageFill Example: TiledFill TiledFill is a Fill subclass that wraps another Fill object. TiledFill uses the wrapped Fill object to paint each tileWidth x tileHeight cell in a grid whose bounds are defined by the paintFill rectangle. The paintFill method for TiledFill paints the first tile in the upper left corner of the incoming rectangle. Here is the painting code: public void paintFill(Component c, Graphics g, Rectangle r) { int x = r.x, y = r.y, w = r.width, h = r.height; ... Graphics clippedG = g.create(x, y, w, h); for (int tx = 0; tx < w; tx += tileWidth) { for (int ty = 0; ty < h; ty += tileHeight) { tile.paintFill(c, clippedG, tx, ty, tileWidth, tileHeight); } } clippedG.dispose(); } The paintFill method clips its graphics object because the full size tiles painted in the rightmost column and bottom row may extend beyond the paintFill rectangle. The diagram below illustrates this. The TestTiledFill application combines a TiledFill object with an ImageFill object to paint a window. The composite object, tiledFill, is created like this: BufferedImage image = ImageIO.read(file); ImageFill fill = new ImageFill(image); tiledFill = new TiledFill(fill, image.getWidth(), image.getHeight()); The paintComponent method of TestTiledFill is a little more complicated than the usual implementation because we want to paint the area within the clipBounds of the incoming paintComponent graphics object with tiles that are aligned with a tileWidth by tileHeight grid whose origin is at 0,0. To do so we create a paintFill rectangle that's based on the clipBounds: we enlarge the clipBounds rectangle so that its origin is aligned with the origin of a tile and its size is a multiple of the tile size. The TestTiledFill application allows you to interactively change the tile size by dragging on the lower right corner of the green rectangle. You can find the code for this example in TestTiledFill.java and TiledFill.java. (You also need Fill.java and ImageFill.java.) You can run the example by downloading the examples JAR file and then running TestTiledFill, using J2SE v 1.4, like this: java -cp examples.jar TestTiledFill Example: BorderFill Our final example is a Fill subclass called BorderFill that uses an array of eight Fill objects to paint a border. Each of the corners and each of the sides has a Fill object. An array of eight Dimension objects defines the "natural size" of the Fill objects. The four corners are always painted with their natural size; the four sides are scaled vertically or horizontally to fit. The diagram below shows how the eight Fills are arranged: Because a common application of BorderFill is painting a border based on eight small images, we provide constructors to simplify this case: BorderFill(BufferedImage image, Rectangle[] rectangles, boolean[] isTile) BorderFill(BufferedImage image, Rectangle[] rectangles) The first constructor extracts the eight subimages specified by the array of rectangles and creates an ImageFill object for each one. If any sides should be tiled, the constructor wraps a TiledFill object around the ImageFill for each tiled side. You specify the sides to be tiled with the tile array, where isTile[n] specifies whether fill object n*2 should be tiled or scaled to fit. For example, to specify that only the right and bottom be tiled, you need a tile array of {false, true, true, false}. This means that fill object 0 (the top) should not be tiled, 2 (right) and 4 (bottom) should be, and 6 (left) should not be tiled. The second BorderFill constructor is a convenience that calls the first with an isTile array of {true, false, true, false}. This makes each corner be painted with a fixed size ImageFill, the vertical sides of the border be painted with a ImageFill scaled (vertically) to fit, and the horizontal sides be painted tiled with TiledFill objects. This particular arrangement of images is common in borders painted for look and feel "skins", such as GTK+ themes. It is well suited to defining borders for user interface components such as text fields that tend to be much wider than they are high. Because the BorderFill class implements the Border interface, a BorderFill object can be used as a border for any Swing component. The TestBorderFill application contains a single panel whose border is implemented by a BorderFill object. You can drag out copies of the panel with the left mouse button and configure a few properties with the panel's right button menu. To move or resize a bordered panel, press the left mouse button near the panel's edge and drag. You can find the code for this example in TestBorderFill.java and BorderFill.java. (You also need Fill.java, ImageFill.java, and TiledFill.java.) You can run the example by downloading the examples JAR file and then running TestBorderFill, using J2SE v 1.4, like this: java -cp examples.jar TestBorderFill A Border Editor To help you use BorderFill, we've written a border editor. It lets you easily define the eight border parts, given an image of a complete "prototypical" border. The following screenshot shows the border editor in action. As you can see, the user is working with a picture of a border that has been magnified to simplify defining the precise boundaries of the border parts. To launch the border editor, download editor.jar and then run it using the -jar option of the java command. For example: e:/java/jdk1.4/bin/java -jar editor.jar If you'd like to look at the source code for the border editor, download editor.zip. To compile the border editor, you also need the source files from border.zip. To read the instructions for using the border editor, choose the Using BorderEditor... item from the border editor's Help menu. Acknowledgments The examples in this article depend heavily on other people's work. BorderEditor Application Scott Violet wrote the border editor application. The original plan for building the editor was to spend an afternoon banging something out and then, well, just live with it. As it turned out, usability issues turned this into a longer project then we expected and eventually we had to stop working on it. The editor is quite usable as it is. However, it also seems to be holding out a tantalizing opportunity to build something exceptional, something that would be usable by those folks whose stock-in-trade isn't Java. TestTiledFill Image The image used in TestTiledFill was created by Marc Hesse, titled "Seamless Texture 39", and can be found in his gallery at marc-h.deviantart.com. View the original. TestImageFill Image The image used in TestImageFill was created by Kit Schroeder, titled "Marble", and can be found in her gallery at euphoria.deviantart.com View the original. For More Information See Amy Fowler's article Painting in AWT and Swing.