The JFC Swing Tutorial: A Guide to Constructing GUIs (2nd Edition)
< Day Day Up > |
Many Swing components , such as labels, buttons , and tabbed panes, can be decorated with an icon a fixed- sized picture. An icon is an object that adheres to the Icon [54] interface. Swing provides a particularly useful implementation of the Icon interface: ImageIcon , [55] which paints an icon from a GIF, JPEG, or PNG image. [56] [54] Icon API documentation: http://java.sun.com/j2se/1.4.2/docs/api/javax/swing/Icon.html. [55] ImageIcon API documentation: http://java.sun.com/j2se/1.4.2/docs/api/javax/swing/ImageIcon.html. [56] The PNG format has been supported since v1.3 of the Java platform. Here's a snapshot of an application with three labels (see Figure 30), two decorated with an icon: Figure 30. An application that decorates two labels with an icon.
The program uses one image icon to contain and paint the yellow splats. One statement creates the image icon and two more statements include the image icon on each of the two labels: ImageIcon icon = createImageIcon("images/middle.gif", "a pretty but meaningless splat"); label1 = new JLabel("Image and Text", icon, JLabel.CENTER); ... label3 = new JLabel(icon); The createImageIcon method (used in the preceding snippet) is one we use in many of our code samples. It finds the specified file and returns an ImageIcon for that file, or null if that file couldn't be found. The following is a typical implementation: /** Returns an ImageIcon, or null if the path was invalid. */ protected static ImageIcon createImageIcon(String path, String description) { java.net.URL imgURL = LabelDemo.class.getResource(path); if (imgURL != null) { return new ImageIcon(imgURL, description); } else { System.err.println("Couldn't find file: " + path); return null; } } If you copy createImageIcon , be sure to change the name of the class used for the getResource call; it should be the name of the class that contains the createAppletImageIcon method. For example, you might change LabelDemo.class.getResource to MyApp .class.getResource . In the preceding snippet, the first argument to the ImageIcon constructor is relative to the location of the class LabelDemo , and will be resolved to an absolute URL. The description argument is a string that allows assistive technologies to help a visually impaired user understand what information the icon conveys. [57] [57] See How to Support Assistive Technologies (page 519) for more information. Generally , applications provide their own set of images used as part of the application, as is the case with the images used by many of our demos. You should use the ClassgetResource method to obtain the path to the image. This allows the application to verify that the image is available and to provide sensible error handling if it is not. When the image is not part of the application, getResource should not be used and the ImageIcon constructor is used directly. For example: ImageIcon icon = new ImageIcon("/home/sharonz/images/middle.gif", "a pretty but meaningless splat"); When you specify a file name or a URL to an ImageIcon constructor, processing is blocked until after the image data is completely loaded or the data location has proven to be invalid. If the data location is invalid (but non-null), an ImageIcon is still successfully created; it just has no size and, therefore, paints nothing. As we showed in the createImageIcon method, it's wise to first verify that the URL points to an existing file before passing it to the ImageIcon constructor. This allows graceful error handling when the file isn't present. If you want more information while the image is loading, you can register an observer on an image icon by calling its setImageObserver method. Under the covers, each image icon uses an Image [58] object to hold the image data and a MediaTracker [59] object, which is shared by all image icons in the same program, to keep track of the image's loading status. [58] Image API documentation: http://java.sun.com/j2se/1.4.2/docs/api/java/awt/Image.html. [59] MediaTracker API documentation: http://java.sun.com/j2se/1.4.2/docs/api/java/awt/MediaTracker.html. A More Complex Image Icon Example
Here's an applet that uses eight image icons (Figure 31). In the snapshot, you can see three of them: one displays the photograph and two decorate buttons with small arrows. Figure 31. A screenshot of the IconDemoApplet example.
Try This:
IconDemoApplet demonstrates icons used in the following ways:
The following code creates the four arrow image icons and attaches them to the two buttons. //Create the next and previous buttons. ImageIcon nextIcon = createAppletImageIcon("images/right.gif", "a right arrow"); ImageIcon dimmedNextIcon = createAppletImageIcon( "imagesdimmedRight.gif", "a dimmed right arrow"); ImageIcon previousIcon = createAppletImageIcon("images/left.gif", "a left arrow"); ImageIcon dimmedPreviousIcon = createAppletImageIcon( "images/dimmedLeft.gif", "a dimmed left arrow"); nextButton = new JButton("Next Picture", nextIcon); nextButton.setDisabledIcon(dimmedNextIcon); nextButton.setVerticalTextPosition(AbstractButton.CENTER); nextButton.setHorizontalTextPosition(AbstractButton.LEFT); ... previousButton = new JButton("Previous Picture", previousIcon); previousButton.setDisabledIcon(dimmedPreviousIcon); previousButton.setVerticalTextPosition(AbstractButton.CENTER); previousButton.setHorizontalTextPosition(AbstractButton.RIGHT); The action handler for the buttons initiates loading of the photographs into the image icon: //User clicked either the next or the previous button. public void actionPerformed(ActionEvent e) { //Show loading message. photographLabel.setIcon(null); photographLabel.setText("Loading image..."); //Compute index of photograph to view. if (e.getActionCommand().equals("next")) { current += 1; if (!previousButton.isEnabled()) previousButton.setEnabled(true); if (current == pictures.size() - 1) nextButton.setEnabled(false); } else { current -= 1; if (!nextButton.isEnabled()) nextButton.setEnabled(true); if (current == 0) previousButton.setEnabled(false); } //Get the photo object. Photo pic = (Photo)pictures.elementAt(current); //Update the caption and number labels. captionLabel.setText(pic.caption); numberLabel.setText("Picture " + (current+1) + " of " + pictures.size()); //Update the photograph. ImageIcon icon = pic.getIcon(); if (icon == null) { //haven't viewed this photo before loadImage(imagedir + pic.filename, current); } else { updatePhotograph(current, pic); } } The photographs are loaded in a separate thread by the loadImage methodits code is shown a little later in this section. The Photo class is a simple class that manages an image icon and its properties. public class Photo { public String filename; public String caption; public int width; public int height; public ImageIcon icon; public Photo(String filename, String caption, int w, int h) { this.filename = filename; if (caption == null) this.caption = filename; else this.caption = caption; width = w; height = h; icon = null; } public void setIcon(ImageIcon i) { icon = i; } public ImageIcon getIcon() { return icon; } } The section Loading Images into Applets (page 613) discusses how images are loaded into this applet and shows the createAppletImageIcon methoda version of createImageIcon customized for applets that are deployed using Java Plug-in. Loading Images Using getResource
Most often an image icon's data comes from an image file. There are a number of valid ways that your application's class and image files can be configured on your file server. You might have your class files in a JAR file, or your image files in a JAR file; they might be in the same JAR file, or they might be in different JAR files. Figures 32 through 35 illustrate a few of the ways these files can be configured. For all of the pictured configurations, you can get the URL for myImage.gif by invoking getResource with the argument images/myImage.gif . Figure 32. Class file next to an image directory containing the image file, in GIF format.
Figure 35. Class and image files in the same JAR file.
Figure 33. Class file in same directory as JAR file. The JAR file was created with all the images in an images directory.
Figure 34. Class file in one JAR file and the images in another JAR file.
If you are writing a real-world application, it is likely (and recommended) that you put your files into a package. For more information on packages, see the section "Creating and Using Packages" in The Java Tutorial . [61] Figures 36 through 38 show some possible configurations using a package named omega . [61] You can find "Creating and Using Packages" online and on the CD at: JavaTutorial/java/interpack/packages.html . Figure 36. Class file in directory named omega ; the image is in omega/images directory.
Figure 38. One big JAR file with class files under omega directory and image files under omega/images directory.
Figure 37. The class file is in omega directory; the image is in the JAR file not inside of omega directory, but created within the omega/images hierarchy.
As before, you can get the URL for myImage.gif by specifying images/myImage.gif to getResource . The following code reads the image, for any of the configurations pictured in Figures 32 through 38: java.net.URL imageURL = myDemo.class.getResource("images/myImage.gif"); ... if (imageURL != null) { ImageIcon icon = newImageIcon(imageURL); } The getResource method causes the class loader to look through the directories and JAR files in the program's class path, returning a URL as soon as it finds the desired file. In our example, the MyDemo program attempts to load the image/myImage.gif file. The class loader looks through the directories and JAR files in the program's class path for image/myImage.gif . If the class loader finds the file, it returns the URL of the JAR file or directory that contained the file. If another JAR file or directory in the class path contains the images/myImage.gif file, the class loader returns the first instance that contains the file.
Version Note: In versions of Java Plug-in before 1.4, getResource doesn't look in JAR files. See Loading Images into Applets (page 613) for details.
Here are some ways to specify the class path:
Most of our examples put the images in an images directory under the directory that contains the examples' class files. When we create JAR files for the examples, we keep the same relative locations, although often we put the class files in a different JAR file than the image JAR file. No matter where the class and image files are in the file systemin one JAR file, or in multiple JAR files, in a named package, or in the default packagethe same code finds the image files using getResource . For more information, see "Accessing Resources in a Location-Independent Manner" [63] and the "Application Development Considerations." [64] [63] "Accessing Resources in a Location-Independent Manner" is online at: http://java.sun.com/j2se/1.4.2/docs/guide/resources/resources.html. [64] "Application Development Considerations" is online at: http://java.sun.com/j2se/1.4.2/docs/guide/jws/developersguide/development.html. Loading Images into Applets
Applets generally load image data from the computer that served up the applet. There are two reasons for this. First, untrusted applets can't read from the file system on which they're running. Second, it just makes sense to put an applet's class and data files together on the server. The IconDemoApplet program initializes each of its image icons from GIF files whose locations are specified with URLs. Because IconDemoApplet is designed to be an untrusted applet, we must place the image files under the applet's code base (the server directory containing the applet's class files). The following figure shows the locations of files for Icon-DemoApplet . Figure 39. The image files or image directory can be deleted if the applet loads image files from a JAR file.
The APPLET tag is where you specify information about the images used in the applet. For example, here's the code for part of the tag for IconDemoApplet : <applet code="IconDemoApplet.class" codebase="example-1dot4/" archive="iconAppletClasses.jar, iconStartupImages.jar, iconAppletImages.jar" width="400" height="360"> <param name="IMAGEDIR" VALUE="images"> <param name="IMAGE0" VALUE="stickerface.gif"> <param name="CAPTION0" VALUE="Sticker Face"> <param name="WIDTH0" VALUE="230"> <param name="HEIGHT0" VALUE="238"> ... </applet> The IMAGEDIR parameter indicates that the image files should be in a directory named images relative to the applet's code base. Applets generally use a URL that is constructed relative to the applet's code base. As you can see from the archive attribute of the preceding APPLET tag, we have deployed IconDemoApplet using three JAR files. The classes are in one JAR file, the images required for starting up the UI (the arrow images) are in another JAR file, and the rest of the images are in a third JAR file. Separating the UI images from the other images means a quicker start-up time. [65] [65] For more information on specifying JAR files with the APPLET tag, see these sections on the CD in The Java Tutorial : "Using the APPLET Tag" ( JavaTutorial/applet/appletsonly/html.html ) and the "JAR File Overview" ( JavaTutorial/jar/overview.html ). When using Java Web Start to deploy an applet, you can use the same approach for loading resources as you do for applicationsthe getResource method. However, for applets deployed using Java Plug-in, getResourceAsStream is more efficient for loading images.
Version Note: Prior to release 1.4, when called from an applet deployed using Java Plug-in, getResource did not look in JAR files for resources, it looked only in the code base. In this situation, you must either put the images in the code base, or you must use getResourceAsStream .
Here's the code from IconDemoApplet that reads the images using getResourceAsStream : public class IconDemoApplet extends JApplet ... { protected String leftButtonFilename = "images/left.gif"; ... public void init() { ... ImageIcon leftButtonIcon = createAppletImageIcon(leftButtonFilename, "an arrow pointing left"); ... } ... //Returns an ImageIcon, or null if the path was invalid. //When running an applet using Java Plug-in, //getResourceAsStream is more efficient than getResource. protected static ImageIcon createAppletImageIcon(String path, String description) { int MAX_IMAGE_SIZE = 75000; //Change this to the size of //your biggest image, in bytes. int count = 0; BufferedInputStream imgStream = new BufferedInputStream( IconDemoApplet.class.getResourceAsStream(path)); if (imgStream != null) { byte buf[] = new byte[MAX_IMAGE_SIZE]; try { count = imgStream.read(buf); } catch (IOException ieo) { System.err.println("Couldn't read stream from file: " + path); } try { imgStream.close(); } catch (IOException ieo) { System.err.println("Can't close file " + path); } if (count <= 0) { System.err.println("Empty file: " + path); return null; } return new ImageIcon(Toolkit.getDefaultToolkit(). createImage(buf), description); } else { System.err.println("Couldn't find file: " + path); return null; } } ... } You might want to copy the createAppletImageIcon method for use in your applet. Be sure to change the IconDemoApplet string in the call to getResourceAsStream to the name of your applet. Improving Perceived Performance When Loading Image Icons
Because the photograph images are large, IconDemoApplet uses several techniques to improve the performance of the program as perceived by the user. Providing Dimmed Icons
The applet provides dimmed versions of the arrows for the buttons (code follows ): imagedir = getParameter("IMAGEDIR"); if (imagedir != null) imagedir = imagedir + "/"; ... ImageIcon dimmedNextIcon = createAppletImageIcon( "images/dimmedRight.gif", "a dimmed right arrow"); ImageIcon dimmedPreviousIcon = createAppletImageIcon( "images/dimmedLeft.gif", "a dimmed left arrow"); ... nextButton.setDisabledIcon(dimmedNextIcon); ... previousButton.setDisabledIcon(dimmedPreviousIcon); Without this code, the dimmed versions of the arrows would be computed, which would cause a slight delay the first time each button is dimmed. Basically, this technique trades a noticeable delay when the user clicks the buttons for a smaller, less noticeable delay in the init method. An alternative would be to load the dimmed icons in a background thread after the GUI has been created and shown. This applet uses four separate image files just to display arrows on two buttons. The performance impact of these little images can add up, especially if the browser in which the applet is running uses a separate HTTP connection to load each one. A faster alternative is to implement a custom Icon that paints the arrows. See Creating a Custom Icon Implementation (page 618) for an example. Lazy Image Loading
The applet's initialization code loads only the first photograph. Each other photograph gets loaded when the user first requests to see it. By loading images if and when needed, the applet avoids a long initialization. The downside is that the user has to wait to see each photograph. We try to make this wait less noticeable by providing feedback about the image loading and allowing the user to use the GUI while the image is loading. Not all programs can benefit from lazy loading. For example, the TumbleItem.java applet performs an animation, and all of the images in the animation are needed up-front. That applet's initialization code causes the images to be loaded in a background thread so that the applet can present a GUI (a " Loading Images... " label) before the images have loaded. Background Image Loading
The applet uses a SwingWorker [66] to load each photograph image in a background thread. Because the image is loaded in a separate thread, the user can still click the buttons and otherwise interact with the applet while the image is loading. [66] See also Using the SwingWorker Class (page 636). Here's the code to load each image: private void loadImage(final String imagePath, final int index) { final SwingWorker worker = new SwingWorker() { ImageIcon icon = null; public Object construct() { icon = createAppletImageIcon(imagePath, "photo #", + index); return icon; //return value not used by this program } public void finished() { Photo pic = (Photo)pictures.elementAt(index); pic.setIcon(icon); if (index == current) updatePhotograph(index, pic); } }; worker.start(); } The construct method, which creates the image icon for the photograph, is invoked by the thread that's created by the SwingWorker constructor and started by the start method. After the image icon is fully loaded, the finished method is called. The finished method is guaranteed to execute on the event-dispatching thread, so it can safely update the GUI to display the photograph. Status Updates
While the image is loading in the background, the applet displays a status message: photographLabel.setIcon(null); photographLabel.setText("Loading image..."); This lets the user know that the program is doing something. After the image is loaded, the applet displays the photograph in the viewing area. Caching
After each photograph is viewed for the first time, the applet caches the image icon for later use. Thus if the user revisits a photograph, the program can use the same image icon and display the photograph quickly. If you write a program without caching image icons, it may appear that some implicit image caching is going on within the Java platform. However, this is a side effect of the implementation and is not guaranteed. If your program uses one image in many places in its GUI, you can create the image icon once and use the same instance multiple times. As with all performance- related issues, these techniques are applicable in some situations and not others. These are not general recommendations for all programs, but some techniques you can try to improve the user's experience. Furthermore, the techniques described here are designed to improve the program's perceived performance, but don't necessarily impact its real performance. Creating a Custom Icon Implementation
If an image differs depending on the state of the component it's within, consider implementing a custom Icon class to paint the image. The really nice thing about a custom icon is that you can easily change the icon's appearance to reflect its host component's state. Look-and-feel implementations often use custom icons. For example, the Java look and feel uses a single MetalCheckBoxIcon object to paint all of the check boxes in the GUI. The MetalCheckBoxIcon paints itself differently depending on whether its host component is enabled, pressed, or selected.
ImageIcon leftButtonIcon = createImageIcon("images/right.gif", "an arrow pointing right"); ... ImageIcon rightButtonIcon = createImageIcon("images/left.gif", "an arrow pointing left"); b1 = new JButton("Disable middle button", leftButtonIcon); ... b3 = new JButton("Enable middle button", rightButtonIcon);
Icon leftButtonIcon = new ArrowIcon(SwingConstants.TRAILING); ... Icon rightButtonIcon = new ArrowIcon(SwingConstants.LEADING); b1 = new JButton("Disable middle button", leftButtonIcon); ... b3 = new JButton("Enable middle button", rightButtonIcon); You can find the implementation of the custom icon class in ArrowIcon.java . [69] Here are the interesting parts of its code: [69] You can find the ArrowIcon source file here: JavaTutorial/uiswing/misc/example-1dot4/ArrowIcon.java . class ArrowIcon implements Icon, SwingConstants { public ArrowIcon(int direction) { if (direction == LEADING) { xPoints[0] = width; yPoints[0] = -1; xPoints[1] = width; yPoints[1] = height; xPoints[2] = 0; yPoints[2] = height/2; xPoints[3] = 0; yPoints[3] = height/2 - 1; } else /* direction == TRAILING */ { xPoints[0] = 0; yPoints[0] = -1; xPoints[1] = 0; yPoints[1] = height; xPoints[2] = width; yPoints[2] = height/2; xPoints[3] = width; yPoints[3] = height/2 - 1; } } ... public void paintIcon(Component c, Graphics g, int x, int y) { if (c.isEnabled()) { g.setColor(c.setForeground()); } else { g.setColor(Color.gray); } g.translate(x, y); g.fillPolygon(xPoints, yPoints, xPoints.length); g.translate(-x, -y); //Restore Graphics object } } Note that the icon sets the current color. If you don't do this, then the icon's painting might not be visible. For more information about painting, see Chapter 6, Performing Custom Painting (page 129). Using a custom icon to paint the arrows has a few implications:
The Image Icon API
Tables 18 through 20 list the commonly used ImageIcon constructors and methods . Note that ImageIcon is not a descendent of JComponent or even of Component . You can refer to the API documentation for ImageIcon at: http://java.sun.com/j2se/1.4.2/docs/api/javax/swing/ImageIcon.html. Table 18. Setting, Getting, and Painting the Image Icon's Image
Table 19. Setting or Getting Information about the Image Icon
Table 20. Watching the Image Icon's Image Load
Examples That Use Icons
The following table lists just a few of the many examples that use ImageIcon .
|
< Day Day Up > |