Add Translucence to Menus





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 menua 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.

Make the Custom Menu Item

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 Figure.

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);
		}

	}

No constructor is required because all UI classes have a no-arg constructor automatically. All UI classes also need a static createUI() method to create a new instance of the class, as you can see in the example. In the paint() method, instead of drawing on the graphics object passed in, the code creates a buffered image with the same dimensions as the component, and then calls super.paint(). This will draw the component onto the buffered image instead of the screen. Once the painting is done, it can apply a transform and then draw the image buffer onto the real Graphics. In this case, the transform is an alpha composite of 0.8. This means that instead of drawing the buffer as is, it will draw the buffer partially transparent (80% solid, in this case). This will draw the bufferedimage into the real graphics with a translucent effect. You can vary the strength of the translucency by modifying the second parameter to the AlphaComposite.getInstance() method (1 results in a solid, 0 is totally transparent).

Add a Custom JMenu

If you stopped with just the custom menu items, the menus would seem a bit translucent, but the rest of the window wouldn't shine through. This is because the menu items are inside of another component; in fact, they're inside of three! The JMenu puts all of the menu items inside of a JPopupMenu, which is placed inside of a JPanel, and then the whole deal is put in a layered pane at the top of the frame. The layered pane is already transparent, so you don't need to worry about it, but the JPanel and JPopupMenu are going to be a problem. Figure handles the custom UI for these.

Handling translucence for JPanels and JPopupMenus
	public class CustomPopupMenuUI extends BasicPopupMenuUI {
		public static ComponentUI createUI(JComponent c) {
			return new CustomPopupMenuUI();
		}

		public void installUI(JComponent c) {
			super.installUI(c);
			popupMenu.setOpaque(false);
		}

		public Popup getPopup(JPopupMenu popup, int x, int y) {
			Popup pp = super.getPopup(popup,x,y);
			JPanel panel = (JPanel)popup.getParent();
			panel.setOpaque(false);
			return pp;
		}

	}

The custom pop-up menu UI used here is similar to the CustomMenuItemUI (from Figure). It has a static create UI menu and no constructor. The pop-up menu is already stored as a protected member of the BasicPopupMenuUI parent class, so I can access it easily. The installUI() method is called right after the JPopupMenu is created, so this is the best place to put a call to setOpaque(false). For most L&Fs, this will make the component transparent.

That takes care of the pop-up menu, but what about the parent JPanel? The JPanel is created and initialized deep within the javax.swing.PopupFactory class, so it's pretty well out of reach. This is one place where having access to the JRE source code is invaluable. Without that, this entire hack would have been impossible to figure out. Fortunately, we have access to the finished JPopupMenu from within the getPopup method. I overrode that to call the superclass and then grab the newly minted parent of the pop-up menu and cast it to a JPanel. Now, I can finally set it to be transparent, too.

Test It Out

With your two custom UI classes in place, test them out with Figure, which shows a frame containing two sets of menus and a few components. Before creating any components, the program installed the custom UI classes with two calls to UIManager.put().

Any time you want to override part of a L&F, you can use UIManager.put().


Testing out the translucent menus
	public class MenuTest {
		public static void main(String[] args) throws Exception { 
			UIManager.put("PopupMenuUI","CustomPopupMenuUI"); 
			UIManager.put("MenuItemUI","CustomMenuItemUI");
			JFrame frame = new JFrame();
			JMenuBar mb = new JMenuBar();
			frame.setJMenuBar(mb);
			JMenu menu = new JMenu("File");
			mb.add(menu);
			menu.add(new JMenuItem("Open"));
			menu.add(new JMenuItem("Save"));
			menu.add(new JMenuItem("Close"));
			menu.add(new JMenuItem("Exit"));
			menu = new JMenu("Edit");
			mb.add(menu);

			menu.add(new JMenuItem("Cut"));
			menu.add(new JMenuItem("Copy"));
			menu.add(new JMenuItem("Paste"));
			menu.add(new JMenuItem("Paste Special.."));
			frame.getContentPane().setLayout(new BorderLayout());
			frame.getContentPane().add("North",new JButton("Button"));
			frame.getContentPane().add("Center",new JLabel("a label"));
			frame.getContentPane().add("South",new JCheckBox("checkbox"));
			frame.pack();
			frame.setSize(200,150);
			frame.show();

		}
	}

With all of the code in place, you can compile it and get something that looks like Figure.

Translucent menu


One bug you will notice is that after you open the menu and start moving the cursor between menu items, the background won't shine through anymore. This is because Swing, in an effort to speed up the UI, only repaints the parts it knows have changed. It repaints the menu item, but not the frame contents below (the button and label, in this case) because it thinks they are obscured by the menu item. Of course, the menu item is translucent, so the components should shine through, but Swing doesn't know that. To fix the problem, you'll need to develop a full repaint manager [Hack #53] that will force Swing to always repaint the entire component tree, instead of just the menu items. It's a bit slower, but worth it if you really want this effect:

	UIManager.put("MenuItemUI","CustomMenuItemUI");
	RepaintManager.setCurrentManager(new FullRepaintManager());

One more bug is that the menu must fit within the frame. There are two kinds of menus in Swing: heavyweight and lightweight. Lightweight menus are normal Swing components. Heavyweight menus, on the other hand, are drawn in their own top-level window. This means that there are two windows being drawn: one for the real frame and one for the menu. If you use heavyweight menus, the effect will stop completely because the windows themselves can't be transparent. Normally, Swing will use lightweight menus, but if the menu has to be drawn outside of the framewhich can happen if you have a small window or a really large menuthen it will switch to heavyweight menus automatically and nothing can switch it back until the application restarts. This means you should always make sure your menus fit inside of your windows.

Future Ideas

This hack shows just one example of how you can completely change a component's behavior by customizing its Look and Feel class. Java2D gives you the power to create a wide variety of graphical hacks. As an extension of this technique, you could try blurring the components underneath the menu or create a properly smoothed drop shadow.


     Python   SQL   Java   php   Perl 
     game development   web development   internet   *nix   graphics   hardware 
     telecommunications   C++ 
     Flash   Active Directory   Windows