BUY THIS BOOK
Add to Cart

Print Book $29.95


Add to Cart

Print+PDF $38.94

Add to Cart

PDF $23.99

Safari Books Online

What is this?

Add to UK Cart

Print Book £20.95

What is this?

Looking to Reprint or License this content?


Swing Hacks
Swing Hacks Tips and Tools for Killer GUIs By Joshua Marinacci, Chris Adamson
June 2005
Pages: 542

Cover | Table of Contents | Colophon


Table of Contents

Chapter 1: Basic JComponents
Swing is a powerful toolkit, filled to the brim with complicated components, extension APIs, and large Model-View-Controller (MVC) systems. It can be quite daunting. The current edition of O'Reilly's Java Swing book now stretches over 1,200 pages! Swing now extends from the simplest JButton to the full Look and Feel API. I am still amazed at the power and flexibility of Swing, and quite aware of its complexity. Some of the more esoteric parts can take years to master. However, you don't need to go straight into the JTree or Look and Feel APIs just to do something cool. There are still a lot of fun things waiting in the standard components we don't always think about.
This chapter covers some of the basic components that every Swing developer uses: buttons, labels, menus, and the occasional scroll pane. From this base you will learn how to create image buttons, put watermarks into your text areas, and even build a new component or two. These are the components that seem boring, but with a little imagination, they can do a whole lot, and the techniques here lay the foundation for even more exciting hacks later in the book.
This hack shows how to use Swing's built-in image support to create a completely custom image-based user interface.
Most Swing applications get their look from a Look and Feel (L&F)—either a standard one provided by the VM or a custom one. L&Fs are a whole lot of work to build and still aren't completely custom. You can redefine a button to look like red stoplights, but then all buttons throughout your application will look like red stoplights. Sometimes all you really want is a look built entirely out of images, much like image-based web navigation.
To give you an idea of where this hack is going, Figure 1-1 shows our target: a frame with a panel containing a label, a button, and a checkbox. The panel, label, and button will be completely drawn with images, using none of the standard L&F. The checkbox will be a standard checkbox, but it should be transparent to fit in with the image background.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Hacks 1–12: Introduction
Swing is a powerful toolkit, filled to the brim with complicated components, extension APIs, and large Model-View-Controller (MVC) systems. It can be quite daunting. The current edition of O'Reilly's Java Swing book now stretches over 1,200 pages! Swing now extends from the simplest JButton to the full Look and Feel API. I am still amazed at the power and flexibility of Swing, and quite aware of its complexity. Some of the more esoteric parts can take years to master. However, you don't need to go straight into the JTree or Look and Feel APIs just to do something cool. There are still a lot of fun things waiting in the standard components we don't always think about.
This chapter covers some of the basic components that every Swing developer uses: buttons, labels, menus, and the occasional scroll pane. From this base you will learn how to create image buttons, put watermarks into your text areas, and even build a new component or two. These are the components that seem boring, but with a little imagination, they can do a whole lot, and the techniques here lay the foundation for even more exciting hacks later in the book.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Create Image-Themed Components
This hack shows how to use Swing's built-in image support to create a completely custom image-based user interface.
Most Swing applications get their look from a Look and Feel (L&F)—either a standard one provided by the VM or a custom one. L&Fs are a whole lot of work to build and still aren't completely custom. You can redefine a button to look like red stoplights, but then all buttons throughout your application will look like red stoplights. Sometimes all you really want is a look built entirely out of images, much like image-based web navigation.
To give you an idea of where this hack is going, Figure 1-1 shows our target: a frame with a panel containing a label, a button, and a checkbox. The panel, label, and button will be completely drawn with images, using none of the standard L&F. The checkbox will be a standard checkbox, but it should be transparent to fit in with the image background.
Figure 1-1: A component rendered with images
The first step toward image nirvana is the background. Because this type of component is quite reusable, I built a subclass of JPanel called ImagePanel, shown in Example 1-1.
Example 1-1. A Custom subclass of JPanel
	public class ImagePanel extends JPanel {
		private Image img;

		public ImagePanel(Image img) {
			this.img = img;
			Dimension size = new Dimension(img.getWidth(null),
							   img.getHeight(null));setSize(size);
			setPreferredSize(size);
			setMinimumSize(size);
			setMaximumSize(size);
			setLayout(null);
		}

	}
The constructor takes the image to draw and saves it for later use in the img variable. Then it calls setSize() and setPreferredSize() with the size of the image. This ensures that the panel will be the size of the image exactly. I had to set the preferred, maximum, and minimum sizes as well—this is because the panel's parent and children may not be using absolute layouts.
Absolute layout means that there is no layout manager to position the components appropriately (which can be set by calling
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Don't Settle for Boring Text Labels
JLabel is a Swing staple; but it's easy to spruce up boring labels with drop shadows, outlines, and even 3D text.
When you want to draw non-editable text, Swing provides only the JLabel. You can change the font, size, color, and even add an icon. By using HTML in your components [Hack #52] , you can even add things like underline and bullets. This is fine for most jobs, but sometimes you need more. What if you want a drop shadow or an embossed effect? The JLabel is simply inadequate for richer interfaces. Fortunately, the Swing Team made it very easy to extend the JLabel and add these features yourself.
A great many text effects can be achieved with two simple features. First, you can draw text multiple times, with each iteration slightly offset or in a different color, to create effects like drop shadows and embossing. Second, you can adjust the spacing between letters in a word (a feature known as tracking in text-processing circles). Tracking is always specified in addition to the default tracking specified by a font. Thus, a tracking of +1 would be drawn as one extra pixel between each letter. A tracking of 0 would have the same spacing as no extra tracking at all.
To implement all of this, you must override both the sizing and the painting code in JLabel, which of course calls for a subclass; see Example 1-5 for details.
Example 1-5. Defining a richer JLabel
	public class RichJLabel extends JLabel {

		private int tracking;
		public RichJLabel(Stringtext, int tracking) {
			super(text);
			this.tracking = tracking;
		}

		private int left_x, left_y, right_x, right_y;
		private Color left_color, right_color;
		public void setLeftShadow(int x, int y, Color color) {
			left_x = x;
			left_y = y;
			left_color = color;
		}
		public void setRightShadow(int x, int y, Color color) {
			right_x = x;
			right_y = y;
			right_color = color;
		}
RichJLabel extends the standard javax.swing.JLabel and adds a tracking argument to the constructor. Next, it adds two methods for the right and left shadow. These are called shadows because they will be drawn below the main text, but whether they actually look like shadows depends on the color, as well as the x- and y-offsets passed into each method.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Fill Your Borders with Pretty Pictures
Swing comes with a set of customizable borders, but sometimes you want more than they provide. This hack shows how to create a completely imagebased border that can be resized.
Swing has a prefabricated border, called the MatteBorder, which can accept an image in its constructor. For simple tiled backgrounds, such as a checkerboard pattern, this works fine. However, if you want to have particular images in each corner, creating a fully resizable image border, then you'll need something more powerful. Fortunately, Swing makes it very easy to create custom border classes. The image border in this hack will produce a border that looks like Figure 1-12.
Figure 1-12: An image-based border
The first step to any custom border is to subclass AbstractBorder and implement the paintBorder() method. The class will take eight images in the constructor, one for each corner and each side; all the code is shown in Example 1-6.
Example 1-6. Building an image-based border
	public class ImageBorder extends AbstractBorder {

		Image top_center, top_left, top_right;
		Image left_center, right_center;
		Image bottom_center, bottom_left, bottom_right;
		Insets insets;

		public ImageBorder(Image top_left, Image top_center, Image top_right,
			Image left_center, Image right_center,
			Image bottom_left, Image bottom_center, Image bottom_right) {

			this.top_left = top_left;
			this.top_center = top_center;
			this.top_right = top_right;
			this.left_center = left_center;
			this.right_center = right_center;
			this.bottom_left = bottom_left;
			this.bottom_center = bottom_center;
			this.bottom_right = bottom_right;
		}

	public voidsetInsets(Insets insets) {
				this.insets = insets;
			}

			public Insets getBorderInsets(Component c) {
				if(insets != null) {
				return insets;
				} else {
				return new Insets(top_center.getHeight(null),
				left_center.getWidth(null),
				bottom_center.getHeight(null), right_center.getWidth(null));
				}
			}
The two methods after the constructor control the border insets. These are the gaps between the panel's outer edge (and its parent) and the inner edge of the panel where the panel's children are drawn.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Display Dates in a Custom Calendar
You can download calendar components from third parties, but real hackers can use Swing to build a custom calendar widget on their own.
When you design an application, you'll often want to use standard widgets to display information. Swing doesn't always give you what you need, though. Consider the calendar component: Swing doesn't come with one, so most users have to download widgets to integrate into their application. However, why not go with a cool and hip teen-friendly application with an attractive, image-based component, as shown in Figure 1-15?
Figure 1-15: Custom calendar component
That would be a bit more fun, wouldn't it? This hack will show you how to build a completely custom calendar component using java.util.Calendar and a few images.
First, consider what you'll need. You've got to have pretty images, a component to paint them on, and then some logic to handle the different parts of the date, including what day of the week starts off the current month. You should also provide a setDate() method, so that MVC frameworks can play well with your calendar. Let's get started.
I created three images in Photoshop: one for the background, one for each day, and one for the current day. These are shown in Figures 1-16, 1-17, and 1-18.
Figure 1-16: calendar.png for the general background
Figure 1-17: day.png for the day backgrounds
Figure 1-18: highlight.png for the current day
I could have separated the day names and the title, but since they don't change, it was simpler to make them part of the image.
The easiest way to create a custom component with fancy drawing is to start off with a JPanel and override the paintComponent() method, as shown in Example 1-9.
Example 1-9. A Calendar base component
	public class CalendarHack extends JPanel {
		protected Image background, highlight, day_img;
		protected SimpleDateFormat month = new SimpleDateFormat("MMMM");
protected SimpleDateFormat year = new SimpleDateFormat("yyyy");
		protected SimpleDateFormat day = new SimpleDateFormat("d");
		protected Date date = new Date();

		public void setDate(Date date) {
			this.date = date;
		}

		publicCalendarHack() {
			background = new ImageIcon("calendar.png").getImage();
			highlight = new ImageIcon("highlight.png").getImage();
			day_img = new ImageIcon("day.png").getImage();
			this.setPreferredSize(new Dimension(300,280));

		}
		
		public void paintComponent(Graphics g) {

			((Graphics2D)g).setRenderingHint(RenderingHints.KEY_ANTIALIASING,
				RenderingHints.VALUE_ANTIALIAS_ON);
			g.drawImage(background,0,0,null);
			g.setColor(Color.black);
			g.setFont(new Font("SansSerif",Font.PLAIN,18));
			g.drawString(month.format(date),34,36);
			g.setColor(Color.white);
			g.drawString(year.format(date),235,36);

		}
	}
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Add a Watermark to a Text Component
This hack will show how to create a custom image background for the JTextField, a complex Swing component that does not already support backgrounds or icons by default.
One of Swing's most underused features is the ability to partially override drawing code. Most programs enhance widgets by using renderers or completely overriding the paint code. By only partially overriding the drawing, however, you can create some very interesting effects that blend both new and existing drawing commands.
Some components, like JList and JTable, use renderers to customize their look. To put a background in a JTextField, however, requires more. The plan is to subclass JTextField, prepare the resources for drawing a background (loading the image, etc.), and then draw a new background while preserving the normal JTextField drawing code for the text and cursor.
The actual drawing will be done with a TexturePaint. Java2D allows you to fill any area with instances of the Paint interface. Typically you use a color, which is an implementation of Paint, but it is possible to use something else, such as a texture or gradient. This class will use a TexturePaint to tile an image across the component's background.
The first step is to create a JTextField subclass (shown in Example 1-10).
Example 1-10. Preparing a field for watermarking
	public class WatermarkTextField extends JTextField {
		BufferedImage img;
		TexturePaint texture;

		public WatermarkTextField(File file) throws IOException {
			super();
			img = ImageIO.read(file);
			Rectangle rect = new Rectangle(0,0,
				img.getWidth(null),img.getHeight(null));
			texture = new TexturePaint(img, rect);
			setOpaque(false);
		}
	}
Example 1-10 creates a class called WatermarkTextField. It is a subclass of JTextField with a custom constructor that accepts a File object containing an image. It also defines two member variables: img and texture. After the obligatory call to super(), the constructor reads the file into the
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Watermark Your Scroll Panes
This hack creates a text area with a tiled background image that is fixed, even when the text area scrolls, and also a fixed foreground image that appears above the text, much like the station badges now affixed to the lower-righthand corner of most TV broadcasts.
The Swing framework was designed to let developers override portions of every component, both the visual appearance (the view) and the behavior (the model and controller). This design gives developers great flexibility. One of my favorites is the JScrollPane. Its nested composite design allows developers to create some stunning effects.
Once again, the idea is to override the drawing code of a standard component to create the visual effects [Hack #5] . The difference here is that you must deal with a composite object, the JScrollPane. A JScrollPane is not a single Swing component—it's actually a wrapper around two scrollbars and the component that does the real scrolling is a JViewport. This viewport is the actual target component; you will subclass it to draw both above and below the View component (as seen in Example 1-12). The View is the Swing widget being scrolled; in this case, it is a JTextArea.
Example 1-12. Modifying the viewport for watermarking
    public class ScrollPaneWatermark extends JViewport {
       BufferedImage fgimage, bgimage;
       TexturePaint texture;
       public void setBackgroundTexture(URL url) throws IOException {
       bgimage = ImageIO.read(url);
       Rectangle rect = new Rectangle(0,0,
               bgimage.getWidth(null),bgimage.getHeight(null));
       texture = new TexturePaint(bgimage, rect);
    }

    public void setForegroundBadge(URL url) throws IOException {
        fgimage = ImageIO.read(url);
    }
The ScrollPaneWatermark class inherits from JViewport, adding two methods: setBackgroundTexture() and setForegroundBadge(). Each takes a URL instead of a File to allow for images loaded from places other than the local disk, such as a web server or JAR file.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Put a NASA Photo into the Background of a Text Area
This hack will repurpose an existing web page, one of NASA's photo sites, by pulling their "Astronomy Picture of the Day" into the background of a text area.
You've already learned how to draw a watermark image in the background of a text area [Hack #6] using a ScrollPaneWatermark. This hack will pull a photo down from the Web and reuse that class to put the photo in the background. The photo itself comes from NASA's "Astronomy Picture of the Day" page: http://antwrp.gsfc.nasa.gov/apod/. The URL to the image changes each day, but the page itself does not. To pull the image down you will load the page, find the image URL, then load the image itself and put it into the ScrollPaneWatermark. Depending on the day, it may look something like Figure 1-22.
Figure 1-22: Text area with a background image
The code in Example 1-14 defines a class called BackgroundLoader, which implements Runnable so it can be placed on its own thread. The constructor takes as an argument the ScrollPaneWatermark, which the loader will put the image into. The run() method contains a loop that will run every two hours, loading the page, finding the SRC URL, then loading the image into the watermark.
Example 1-14. A thread to load a background image
    public class BackgroundLoader implements Runnable {
	    private ScrollPaneWatermark watermark;
		public BackgroundLoader(ScrollPaneWatermark watermark) {
		    this.watermark = watermark;
		}	

		public void run() {
			while(true) {
			try {String base_url = "http://antwrp.gsfc.nasa.gov/apod/"; 
				URL url = new URL(base_url);
				Reader input = new InputStreamReader(url.openStream());
				char buf[] = new char[1024];
				StringBuffer page_buffer = new StringBuffer();
				while(true) {

				int n = input.read(buf); 
				if(n < 0) { break; } 
				page_buffer.append(buf,0,n);
			}
			// Locate the Image URL (see next section)
			} catch (Exception ex) {
				System.out.println("exception: " + ex);
				ex.printStackTrace();

			} 
		} 
	} 
  }
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Animate Transitions Between Tabs
This hack shows how to create animated transitions that play whenever the user switches tabs on a JTabbedPane.
One of Swing's great strengths is that you can hack into virtually anything. In particular, I love making changes to a component's painting code. The ability to do this is one of the reasons I prefer Swing over SWT. Swing gives me the freedom to create completely new UI concepts, such as transitions.
With the standard paint methods, Swing provides most of what you will need to build the transitions. You will have to put together three additional things, however. First, you need to find out when the user actually clicked on a tab to start a transition. Next, you need a thread to control the animation. Finally, since some animations might fade between the old and new tabs, you need a way to provide images of both tabs at the same time. With those three things, you can build any animation you desire.
To keep things tidy, I have implemented this hack as a subclass of JTabbedPane, except for the actual animation drawing, which will be delegated to a further subclass. By putting all of the heavy lifting into the parent class, you will be able to create new animations easily.
Example 1-16 is the basic skeleton of the parent class.
Example 1-16. A skeleton for the transition manager
	public class TransitionTabbedPane extends JTabbedPane 
		implements ChangeListener, Runnable {
		protected int animation_length = 20;
public TransitionTabbedPane() {
			super();this.addChangeListener(this);

		}
		public int getAnimationLength() {
			return this.animation_length;
		}
		
		public void setAnimationLength(int length) {
			this.animation_length = length;
		}
TransitionTabbedPane extends the standard JTabbedPane and also implements ChangeListener and Runnable. ChangeListener allows you to learn when the user has switched between tabs. Since the event is propagated before the new tab is painted, inserting the animation is very easy.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Blur Disabled Components
This hack explores creating how to perform a blur transformation on a Swing component.
Every Swing component draws to the screen via the paintComponent() method. This is true even for components that offload the actual drawing to Look and Feel UI objects. Because all drawing goes through the paintComponent() method at some point, this point is where you can do some interesting things by manipulating the graphics object during the paint process.
Swing components draw to the Graphics object passed in through the paintComponent() method. This means that if you replace the Graphics object with a custom version, you can capture a component's drawing into a bitmap instead of going straight to the screen.
Blurring is a pixel-level operation, meaning the actual blurring is done pixel-by-pixel in a bitmap. By drawing the component to a bitmap, blurring that bitmap, and then drawing the bitmap in the place of the component, you can effectively have a blurred component without disturbing the rest of the Swing painting routines. The particular implementation in this hack uses a blurred effect to replace the normal graying of a component when it is disabled.
The first step is to capture the button into a bitmap, as shown in Example 1-20.
Example 1-20. Creating a blurrable button
	public classBlurJButton extends JButton {
		public BlurJButton(String text) {
			super(text);
		}

		public void paintComponent(Graphics g) {
			if(isEnabled()) {
			super.paintComponent(g);
			return;

		}
		BufferedImage buf = new BufferedImage(getWidth(),getHeight(),
				BufferedImage.TYPE_INT_RGB);        
		super.paintComponent(buf.getGraphics());
		// Blur the buffered image (see next section)
		} 
	}
The BlurJButton class extends a normal JButton and overrides the paintComponent() method. If the button is enabled (neither disabled nor grayed out), then it calls the superclass's normal version of paintComponent() and returns. If the button is disabled, however, then
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Building a Drop-Down Menu Button
This hack shows how to build a color chooser as a proper drop-down component. It will behave like JComboBox but without the extension headaches of Sun's version of the class.
Most custom Swing components are created with simple subclasses of the standard base classes in javax.swing. This works fine most of the time, but every now and then you need to build something where there is no easy standard component to start with. Even worse, sometimes the obvious choice for your starting point is a component so convoluted that you can't figure out where to start. Still, you'd rather not reimplement the wheel. No, I'm not talking about JTree or JTable—I'm referring to the JComboBox. It seems like such a simple component, but the implementation is fiendishly complex.
Most large applications use components that feel like the JComboBox, but do something entirely different, like select a color or show a history list. A quick search through the JComboBox API doesn't turn up any obvious extension points. You could customize it with some cell renderers, but if you need a component that doesn't show a list of data, you are pretty much out of luck. The source to JComboBox is not very helpful either. The work is spread out over several UI classes in the various Look and Feel (L&F) packages. If you did customize one of those, your component would look out of place when used in a different L&F. The only real option is to write your own combo box, which is pretty easy except for the actual drop-down part. You need to show a component on top of the others, poking out of the frame occasionally, but without any decorations of its own. It should be just a borderless floating box. Digging through Swing's source code reveals the secret ingredient: a JWindow.
JWindow is a subclass of Window but not of Frame. This means it has no decorations on the side, and it is hidden from the Dock and Taskbar. This is exactly what you want from a pop up. Care must be taken when creating it, however, as you must ensure the window appears only on top of the existing components, and that it disappears when something else gains focus or the window moves. Fortunately, you can do all of this with one composite component and a few event listeners.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Create Menus with Drop Shadows
This hack explores a simple way to create drop shadows on menus throughout an entire application with minimal code changes.
Many modern operating systems provide menus with interesting effects to make them jump off the screen. One of the most common is the drop shadow. Some programs even provide shadows themselves when the host operating system does not. For years, a lack of low-level graphics support has denied Swing programs access to these kinds of cool effects. But not any more! Most of the effects can be duplicated with Swing's robust theming ability.
Most custom effects require either subclassing a component or messing with graphics overlays. I tried a variety of techniques to create this hack, but I kept coming across the same problem over and over. If I wanted to draw a shadow, I had to change the sizing of each menu item, plus its background, plus the pop-up frame itself. That is a lot of components to manage. It would be a lot simpler if I could tell the components to make themselves a little bit bigger and give me the extra slice of screen real estate to draw in. The solution was right under my nose: the border. Every Swing component can use a custom border, without subclassing, and the border will automatically resize the component to fit. If the border is lopsided, then it will create a kind of shadow effect. Perfect!
Every standard Swing component is actually drawn by a UI helper class, and pop-up menus are no exception. I took the BasicPopupMenuUI in the javax. swing.plaf.basic package and created a subclass called CustomPopupMenuUI (shown in Example 1-25). It only does two things special: adds a custom border to the pop up's parent panel and sets the panel to be transparent.
Example 1-25. Extending the pop-up menu's UI
	public class CustomPopupMenuUI extends BasicPopupMenuUI { 
		public static ComponentUI createUI(JComponent c) {        
			return new CustomPopupMenuUI(); 
		}
		public Popup getPopup(JPopupMenu popup, int x, int y) {
			Popup pp = super.getPopup(popup,x,y);
			JPanel panel = (JPanel)popup.getParent();
			panel.setBorder(newShadowBorder(3,3));
			panel.setOpaque(false);
			return pp;

		}
	}
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Add Translucence to Menus
In this hack I will show you how to add true translucency to your menus with only a slight modification to your program.
Computer interfaces are pretty sophisticated these days. Years ago, we considered ourselves lucky to simply have menu bars at all; now, we need menus with sophisticated effects like animation, shadows, and translucency.
You've already seen how to achieve visual effects by overriding the paint() method of a parent component and then rendering the children into a buffer [Hack #9] . It would be nice to do the same thing here, but there's just one small problem. Overriding the paint() method of the JMenu wouldn't do any good because the JMenu doesn't draw what we think of as a menu—a list of menu items that pop up when you click on the menu's title. The JMenu actually only draws the title at the top of a menu. The rest of the menu is drawn by a JPopupMenu created as a member of the JMenu. Unfortunately this member is marked private, which means you can't substitute your own JPopupMenu subclass for the standard version.
Fortunately there is a way out. Like all Swing components, the menu components delegate their actual drawing to a separate set of Look and Feel classes in the javax.swing.plaf package. If you override the right plaf classes for the menu items and pop-up menu, then you should be able to create the desired translucent effect. It just takes a little subclassing.
All MenuItems are implemented by some form of the javax.swing.plaf. MenuItemUI class. When creating custom UI classes, it is always best to start by subclassing something in the javax.swing.plaf.basic package (in this case, BasicMenuItemUI) because it handles most of the heavy lifting for you, as shown in Example 1-28.
Example 1-28. Extending the basic UI
	public class CustomMenuItemUI extends BasicMenuItemUI {
		public static ComponentUI createUI(JComponent c) {
			return new CustomMenuItemUI();
		}

		public void paint(Graphics g, JComponent comp) {
			// paint to the buffered image
			BufferedImage bufimg = new BufferedImage(

				comp.getWidth(),
				comp.getHeight(),
				BufferedImage.TYPE_INT_ARGB);

			Graphics2D g2 = bufimg.createGraphics();
			// restore the foreground color in case the superclass needs it
			g2.setColor(g.getColor());
			super.paint(g2,comp);
			// do an alpha composite
			Graphics2D gx = (Graphics2D) g;
			gx.setComposite(AlphaComposite.getInstance(
				AlphaComposite.SRC_OVER,0.8f));
			gx.drawImage(bufimg,0,0,null);
		}

	}
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Chapter 2: Lists and Combos
Lists are underrated and underappreciated, and developers who don't appreciate JLists often use JTables when they don't need to. But lists seem to be making a comeback in desktop applications, and with good reason. A lot of the data we deal with are single-dimension collections—search results, recent URLs, downloaded files, etc.—and by making the onscreen version of them more appealing and more usable, a list is the right way to present this data to the user.
Make your 1,000-item list a lot more manageable.
One of the nicest things you can do with a large list is to make it manageable with a filter box. This is a text area that, as you type into it, removes list elements so that only those that contain the typed text are visible.
The hack to do this basically involves having a list model with two representations of its contents: everything that is in the list, and a subset with just the items to be displayed (i.e., those from the first list that match the filter). The model's get methods are then rewired to use only the second list.
The implementation in this hack, FilteredJList, is a single class with two inner subclasses: FilterModel and FilterField. The list owns the field, so a caller can create the JList fairly typically and then just ask for the field and add it wherever it makes sense in the layout.
Start by declaring FilteredJList as a subclass of JList, and provide a constructor and some convenience methods, as seen in Example 2-1.
Example 2-1. FilterList constructor and convenience methods
public classFilteredJList extends JList {

    private FilterField filterField;
    private int DEFAULT_FIELD_WIDTH = 20;
   
    public FilteredJList() {
        super();
        setModel (new FilterModel());
        filterField = new FilterField (DEFAULT_FIELD_WIDTH);

    }

    public void setModel (ListModel m) {
        if (! (m instanceof FilterModel))
            throw new IllegalArgumentException();
        super.setModel (m);
    }

    public void addItem (Object o) {
        (FilterModel)getModel()).addElement (o);
    }

    public JTextField getFilterField() {
        return filterField;
    }
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Hacks 13–20: Introduction
Lists are underrated and underappreciated, and developers who don't appreciate JLists often use JTables when they don't need to. But lists seem to be making a comeback in desktop applications, and with good reason. A lot of the data we deal with are single-dimension collections—search results, recent URLs, downloaded files, etc.—and by making the onscreen version of them more appealing and more usable, a list is the right way to present this data to the user.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Filter JLists
Make your 1,000-item list a lot more manageable.
One of the nicest things you can do with a large list is to make it manageable with a filter box. This is a text area that, as you type into it, removes list elements so that only those that contain the typed text are visible.
The hack to do this basically involves having a list model with two representations of its contents: everything that is in the list, and a subset with just the items to be displayed (i.e., those from the first list that match the filter). The model's get methods are then rewired to use only the second list.
The implementation in this hack, FilteredJList, is a single class with two inner subclasses: FilterModel and FilterField. The list owns the field, so a caller can create the JList fairly typically and then just ask for the field and add it wherever it makes sense in the layout.
Start by declaring FilteredJList as a subclass of JList, and provide a constructor and some convenience methods, as seen in Example 2-1.
Example 2-1. FilterList constructor and convenience methods
public classFilteredJList extends JList {

    private FilterField filterField;
    private int DEFAULT_FIELD_WIDTH = 20;
   
    public FilteredJList() {
        super();
        setModel (new FilterModel());
        filterField = new FilterField (DEFAULT_FIELD_WIDTH);

    }

    public void setModel (ListModel m) {
        if (! (m instanceof FilterModel))
            throw new IllegalArgumentException();
        super.setModel (m);
    }

    public void addItem (Object o) {
        (FilterModel)getModel()).addElement (o);
    }

    public JTextField getFilterField() {
        return filterField;
    }
Notice that along with holding onto the FilterField, the JList also creates its own FilterModel in the constructor, and overrides setModel() to ensure that you can't push in an incompatible model. It also contains an addItem() method, which really just delegates to the FilterModel.
FilterModel, shown in Example 2-2, is where the magic happens.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Add a Filter History
Remember previous searches and research with one click.
Chances are good that if you've searched for something once, it's important enough that you might well search for it again. In Apple's Safari browser, a search widget at the upper right has a little magnifying glass that remembers your last 10 searches. Click the magnifying glass and a pop up appears with the previous searches. Select one and it populates the field and does the search immediately.
Here's an implementation of the same idea, grafted onto the previous hack. In other words, this remembers previous filters. It doesn't remember every keystroke—why bother remembering the searches "J" and "Jo" when you're really just interested in "Joe"—and only adds a search term to the filter when the user presses return.
In the previous hack, you just needed to have a text field and a JList. Now a JButton needs to be attached to the text field, so the two are bundled together in the inner class FilterField. This class is responsible for:
  • Telling the model to refilter on each keystroke in the JTextField, as before.
  • Remembering the JTextField's contents as a saved search anytime the Return or Enter key is pressed.
  • Catching clicks on the JButton and popping up a menu with previous searches.
  • Populating the JTextField with a previous search when one is selected from the list. It doesn't need to explicitly tell the model to refilter because changing the text area will fire a DocumentEvent that is already accounted for by the JTextField's DocumentListener.
Example 2-5 shows the new FilterField class.
Example 2-5. List filtering component with text field and history button
class FilterField extends JComponent
    implements DocumentListener, ActionListener {
    LinkedList prevSearches;
    JTextField textField;
    JButton prevSearchButton;
    JPopupMenu prevSearchMenu;
    public FilterField (int width) {
        super();
        setLayout(new BorderLayout());
        textField = new JTextField (width);
        textField.getDocument().addDocumentListener (this);
        textField.addActionListener (this); 
prevSearchButton =
             new JButton (new ImageIcon ("mag-glass.png"));
        prevSearchButton.setBorder(null);prevSearchButton.addMouseListener (new MouseAdapter() {
                public void mousePressed (MouseEvent me) {                               popMenu (me.getX(), me.getY()); 
                }
             });
        add (prevSearchButton, BorderLayout.WEST);
        add (textField, BorderLayout.CENTER);
        prevSearches = new LinkedList ();

  }
  public void popMenu (int x, int y) {
      prevSearchMenu = new JPopupMenu();
      Iterator it = prevSearches.iterator();
      while (it.hasNext())
          prevSearchMenu.add (               
             new PrevSearchAction(it.next().toString()));
      prevSearchMenu.show (prevSearchButton, x, y);
  }
  public void actionPerformed (ActionEvent e) {
      // called on return/enter, adds term to prevSearches
      if (e.getSource() == textField) {
          prevSearches.addFirst (textField.getText());
          if (prevSearches.size() > 10)
              prevSearches.removeLast();
      }
  }
  public void changedUpdate (DocumentEvent e) {
         ((FilterModel)getModel()).refilter();
  }
  public void insertUpdate (DocumentEvent e) {
         ((FilterModel)getModel()).refilter();
  }
  public void removeUpdate (DocumentEvent e) {
         ((FilterModel)getModel()).refilter();
  }
}
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Make JLists Checkable
Avoid losing 50 selections to an unshifted click.
One horrible UI problem is dealing with vast collections of things that need to be presented to the user and made selectable. If, like me, you've ever had 1,000 emails in your inbox, you know what I mean. Worse, what if you pick a bunch of items to delete, but your finger slips off the key used for multi-selection (Alt on Windows, Command on the Mac, etc.) and you lose all of your previous selections? Overriding the native selection behavior can make this situation somewhat more palatable.
Because a list like this behaves differently than a normal list, it should look different, too, so I've opted for a checkbox metaphor. Each item is shown with a checkbox, and as you click more items, they get checked, and if you select an already-checked item, it gets unchecked.
This turns out to be a little harder than expected. I once did it without JList, creating my own scrolling layout of JPanels and faking the list behavior. It turned up a funny Swing bug because I was using GridBagLayout for the fake list, and it started totally bombing out after about 500 items were added to the list. This was because GridBagLayout has a bug where it can't have more than 512 rows. Considering the bug (number 4254022 on the Java Bug Parade) was filed in 1999 and is still open, I'm figuring it won't get fixed by the time you read this.
The basis of the checkable list is a JList. The tricky part here is that there isn't a way (that I've found) to steal the mouse clicks from the JList and consume them before the normal calls to the ListSelectionModel are made. Instead, the strategy is to set up a ListSelectionListener and just fix everything after JList has done its thing.
To implement the checkbox functionality, subclass JList and give it a custom ListSelectionListener and a ListCellRenderer. Acomplete listing is shown in Example 2-6.
Example 2-6. A checkbox-metaphor JList
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Make Different List Items Look Different
An in-progress download shouldn't look like a completed one.
What made me love lists again were the OmniWeb browser and (later) Safari—particularly, their download managers. By way of negative example, take the download manager for Internet Explorer 5 for Mac…please. This GUI was a table of filenames, URLs, sizes, etc., with columns not even intelligently resized for their widths. OmniWeb, on the other hand, showed a running download with a progress bar, and a finished download with the file location and file size. Safari goes a step further with context-appropriate buttons: an X to cancel an in-progress download, a magnifying glass to locate an already-downloaded file, etc. But it's the same idea: different things shouldn't look the same.
To do this in Swing, you need a hack that goes against everything in all the other Swing books: you need to stop subclassing JComponent when you write a ListCellRenderer. Instead, delegate the getListCellRendererComponent( ) call to one of several components, choosing whichever best represents the item to be rendered.
In fact, the whole tradition of subclassing JComponent for ListCellRenderers is a pretty hateful practice because they're not really used as Components anyway! They're certainly not added to the JList. Instead, a list cell is rendered off screen and those pixels are blitted to the JList. So, provided that what you return in getListCellRendererComponent is what you want the cell to look like, it really doesn't matter how you get there.
By way of demonstration, this hack shows the items in a given directory with different layouts, depending on the file type. All of the cells use an icon on the left, with a name in bold at the top of a two-line layout. However, if the item is a folder, the bottom line contains a count of the children in that folder. If the item is a text file—it ends with one of the various extensions associated with text files (e.g., .txt, .html, .java
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Reorder a JList with Drag-and-Drop
Let users put things where they want.
You may be so used to immutable lists that the idea of reordering a list with drag-and-drop seems unnatural. The first time I saw it—rearranging the order of network devices in Mac OS X to establish a priority (e.g., try Ethernet, then wireless, then modem)—I thought it was kind of odd. In fact, Apple felt it necessary to put a label on the list to tell users they could drag-and-drop the list items to rearrange them. Now that I'm used to it, it's totally cool, and I'd like to see it done in more places.
To implement this functionality in a JList, you basically just have to implement the full set of AWT drag-and-drop interfaces because the list will be both the source of the drag and the target of the drop. The other thing you need to do is to use some cell rendering tricks to provide a visual cue as to where the drop will occur.
The ReorderableJList, shown in Example 2-12, is a JList that uses a DefaultListModel, which is mutable for the obvious reason that it will need to change in response to drag-and-drops. The bulk of it is concerned with implementing the drag-and-drop interfaces DragSourceListener, DropTargetListener, and DragGestureListener. It has an inner class implementing Tranferable to hold the item being dropped, although this isn't absolutely necessary. I could have just held the dragged item in an instance variable and nulled the Transferable in the drag-and-drop calls, but it doesn't hurt to do it the nice way.
Example 2-12. A JList that can be reordered with drag-and-drop
public class ReorderableJList extends JList 
    implements DragSourceListener, DropTargetListener, DragGestureListener {
	
    static DataFlavor localObjectFlavor;
    static {
        try {
            localObjectFlavor =
                new DataFlavor (DataFlavor.javaJVMLocalObjectMimeType);
        } catch (ClassNotFoundException cnfe) { cnfe.printStackTrace(); } 
    } 
    static DataFlavor[] supportedFlavors = { localObjectFlavor }; 
    DragSource dragSource; 
    DropTarget dropTarget; 
    Object dropTargetCell; 
    int draggedIndex = -1;

    public ReorderableJList () {
        super();
        setCellRenderer (new ReorderableListCellRenderer());
        setModel (new DefaultListModel());
        dragSource = new DragSource();
        DragGestureRecognizer dgr =
            dragSource.createDefaultDragGestureRecognizer (this,
                                       DnDConstants.ACTION_MOVE,
                                                           this);
        dropTarget = new DropTarget (this, this);
    }

    // DragGestureListener
    public void dragGestureRecognized (DragGestureEvent dge) {
        System.out.println ("dragGestureRecognized");
        // find object at this x,y
        Point clickPoint = dge.getDragOrigin();
        int index = locationToIndex(clickPoint);
        if (index == -1)
            return;
        Object target = getModel().getElementAt(index);
        Transferable trans = new RJLTransferable (target);
        draggedIndex = index;
        dragSource.startDrag (dge,Cursor.getDefaultCursor(),
                              trans, this);
    }
    // DragSourceListener events
    public void dragDropEnd (DragSourceDropEvent dsde) {
       System.out.println ("dragDropEnd()");
       dropTargetCell = null;
       draggedIndex = -1;
       repaint();
    }
public void dragEnter (DragSourceDragEvent dsde) {}
    public void dragExit (DragSourceEvent dse) {}
    public void dragOver (DragSourceDragEvent dsde) {}
    public void dropActionChanged (DragSourceDragEvent dsde) {}
    // DropTargetListener events
    public void dragEnter (DropTargetDragEvent dtde) {
        System.out.println ("dragEnter");
        if (dtde.getSource() != dropTarget)
            dtde.rejectDrag();
        else {
            dtde.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);
            System.out.println ("accepted dragEnter");
        }
    }
    public void dragExit (DropTargetEvent dte) {}
    // dragOver() listed below
    // drop() listed below
    public void dropActionChanged (DropTargetDragEvent dtde) {}
 
    // main() method to test - listed below

    // RJLTransferable listing below

    // ReorderableListCellRendering listing below 
}
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Animate Your JList Selections
Fading in and catching the eye.
Not every GUI involves windows and mouse pointers, and the visual language of a GUI can be very different depending on what is provided by the environment. Typically, GUIs for things like console video games and settop boxes don't use a mouse metaphor, so there's no onscreen pointer that the user is tracking. As a result, these systems often give the user more profound feedback when they move around a list—highlights slide from one item to another, selected items fade in while deselected items fade out, etc.— so there's something the eye can track. You can do the same thing in Swing, with more cell-rendering hackery. You might not need it now, but it'll be handy if you ever design a kiosk with Swing.
One way to show a changed selection is to show a brief animation of the cell selection. Instead of just being highlighted instantly, you fade the selected cell from its unselected background and foreground colors to its selected colors over the course of a short time (really short, like a half-second, so it isn't annoying).
To do this, you'll need to create an animator thread that kicks off every time the selection changes. This short-lived thread repeatedly updates a highlight color and calls repaint(). The cell renderer can then use the updated highlight color as it redraws the cells in the list. Example 2-18 shows this technique.
Example 2-18. Animating the JList cell selection
import java.awt.*; 
import javax.swing.*; 
import javax.swing.event.*; 
import java.util.*;
public classAnimatedJList extends JList 
    implements ListSelectionListener {

    static java.util.Random rand = new java.util.Random();


    static Color listForeground, listBackground, 
        listSelectionForeground, listSelectionBackground; 
    static float[] foregroundComps, backgroundComps, 
        foregroundSelectionComps, backgroundSelectionComps;

    static {        
        UIDefaults uid = UIManager.getLookAndFeel().getDefaults(); 
        listForeground = uid.getColor ("List.foreground"); 
        listBackground = uid.getColor ("List.background");
        listSelectionForeground = uid.getColor ("List.selectionForeground");
        listSelectionBackground = uid.getColor ("List.selectionBackground");
        foregroundComps =
            listForeground.getRGBColorComponents(null);
        foregroundSelectionComps =
            listSelectionForeground.getRGBColorComponents(null);
backgroundComps =
            listBackground.getRGBColorComponents(null);
backgroundSelectionComps =
            listSelectionBackground.getRGBColorComponents(null); 
    } 
    public Color colorizedSelectionForeground,
        colorizedSelectionBackground;

    public static final int ANIMATION_DURATION = 1000; 
    public static final int ANIMATION_REFRESH = 50;
  
    public AnimatedJList() {        
        super(); 
        addListSelectionListener (this);        
        setCellRenderer (new AnimatedCellRenderer());
    }

    public void valueChanged (ListSelectionEvent lse) {
        if (! lse.getValueIsAdjusting()) {
            HashSet selections = new HashSet();
            for (int i=0; i < getModel().getSize(); i++) {
                if (getSelectionModel().isSelectedIndex(i))
                    selections.add (new Integer(i)); 
            }            
            CellAnimator animator = new CellAnimator (selections.toArray());            
            animator.start();
        } 
    }
public static void main (String[] args) {
        JList list = new AnimatedJList ();
        DefaultListModel defModel = new DefaultListModel();
        list.setModel (defModel);
        String[] listItems = {
            "Chris", "Joshua", "Daniel", "Michael",
            "Don", "Kimi", "Kelly", "Keagan"
    };
    Iterator it = Arrays.asList(listItems).iterator();
    while (it.hasNext())
        defModel.addElement (it.next());
    // show list
    JScrollPane scroller =
        new JScrollPane (list, 
                         ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, 
                         ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
    JFrame frame = new JFrame ("Checkbox JList");
    frame.getContentPane().add (scroller);
    frame.pack();
    frame.setVisible(true);
     }

 class CellAnimator extends Thread {
     Object[] selections;
     long startTime;
     long stopTime;
     public CellAnimator (Object[] s) {
         selections = s;
     }
     public void run() {
         startTime = System.currentTimeMillis(); 
         stopTime = startTime + ANIMATION_DURATION;            
         while (System.currentTimeMillis() < stopTime) {
              colorizeSelections();
              repaint();
              try { Thread.sleep (ANIMATION_REFRESH); }
              catch (InterruptedException ie) {}
         }
         // one more, at 100% selected color
         colorizeSelections();
         repaint();
     }

   // colorizeSelections() listing below

      // AnimatedCellRenderer listing below 
}
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Turn Methods into List Renderers