- An Overview of JLayer and LayerUI
- Demonstrating JLayers Support for Custom Painting
- Detecting Events
- Conclusion
Demonstrating JLayer’s Support for Custom Painting
To demonstrate the custom painting portion of JLayer, I've created a ReverseText application (see Listing 1), which reverses text entered into a textfield when a button is pressed. This application uses JLayer to paint a wallpaper pattern behind the user interface (UI).
Listing 1ReverseText.java
// ReverseText.java import java.awt.Color; import java.awt.EventQueue; import java.awt.GradientPaint; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JLayer; import javax.swing.JPanel; import javax.swing.JTextField; import javax.swing.plaf.LayerUI; public class ReverseText { private static Color PALE_YELLOW = new Color (1.0f, 1.0f, 0.0f, 0.2f); private static Color PALE_GREEN = new Color (0.0f, 1.0f, 0.0f, 0.2f); private static JLayer<JPanel> createLayer () { LayerUI<JPanel> layerUI; layerUI = new LayerUI<JPanel> () { public void paint (Graphics g, JComponent c) { // Paint the wallpaper. Graphics2D g2 = (Graphics2D) g; g2.setPaint (new GradientPaint (0, 0, PALE_YELLOW, 5, 0, PALE_GREEN, true)); g2.fillRect (0, 0, c.getWidth (), c.getHeight ()); // Make sure that layer's panel view is not opaque. JLayer l = (JLayer) c; if (l.getView ().isOpaque ()) ((JPanel) l.getView ()).setOpaque (false); // Paint the view minus its background. super.paint (g, c); } }; // Create a user interface to be decorated. JPanel pnl = new JPanel (); JLabel lblName = new JLabel ("Name:"); pnl.add (lblName); final JTextField txtName = new JTextField (20); pnl.add (txtName); JButton btnReverse = new JButton ("Reverse"); pnl.add (btnReverse); ActionListener al; al = new ActionListener () { public void actionPerformed (ActionEvent ae) { String txt = txtName.getText (); txt = new StringBuffer (txt).reverse ().toString (); txtName.setText (txt); } }; btnReverse.addActionListener (al); // Create the layer for the panel using our custom layerUI. return new JLayer<JPanel> (pnl, layerUI); } private static void createAndShowUI () { JFrame frame = new JFrame ("Reverse Text"); frame.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE); frame.add (createLayer ()); frame.pack (); frame.setLocationRelativeTo (null); frame.setVisible (true); } public static void main (String [] args) { Runnable r = new Runnable () { public void run () { createAndShowUI (); } }; EventQueue.invokeLater (r); } }
The createLayer() method houses the important code. It creates an instance of an anonymous LayerUI subclass, which paints the wallpaper and JPanel view, creates the UI, and wraps the UI's panel container in the layer instance, which the method returns.
Painting takes place in LayerUI's public void paint(Graphics g, JComponent c) method. The second argument is actually a reference to the JLayer instance that wraps the view (the component being decorated)it's not a reference to the view.
After creating a gradient to render the wallpaper behind the view, the paint() method makes sure that the view (a single panel without nested panels) isn't opaque, which would hide the wallpaper, and then paints the view.
Figure 1 shows the UI with the wallpaper background.
Figure 1 The opacity of the gradient colors was reduced to soften the background.
ReverseText demonstrates custom painting while avoiding event notification. Events don't need to be detected because the application is only concerned with painting a wallpaper effect. In contrast, Listing 2 reveals an application that also needs to respond to mouse motion events.
Listing 2BrandedUI.java
// BrandedUI.java import java.awt.AWTEvent; import java.awt.Color; import java.awt.Component; import java.awt.EventQueue; import java.awt.Font; import java.awt.GradientPaint; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GridLayout; import java.awt.Point; import java.awt.event.MouseEvent; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JLayer; import javax.swing.JPanel; import javax.swing.JTextField; import javax.swing.SwingUtilities; import javax.swing.plaf.LayerUI; public class BrandedUI { private static Color PALE_BLUE = new Color (0.0f, 0.0f, 1.0f, 0.3f); private static Color PALE_RED = new Color (1.0f, 0.0f, 0.0f, 0.3f); private static Font BRAND_FONT = new Font ("Arial", Font.BOLD, 18); private static String MSG = "My brand"; private static JLayer<JPanel> createLayer () { LayerUI<JPanel> layerUI; layerUI = new LayerUI<JPanel> () { private Color color = PALE_BLUE; public void installUI (JComponent c) { super.installUI (c); ((JLayer) c).setLayerEventMask (AWTEvent.MOUSE_MOTION_EVENT_MASK); } public void eventDispatched (AWTEvent e, JLayer <? extends JPanel> l) { MouseEvent me = (MouseEvent) e; Point pt = SwingUtilities.convertPoint ((Component) me.getSource (), me.getX (), me.getY (), l); int cx = l.getWidth ()/2; int cy = l.getHeight ()/2; if (pt.x > cx-45 && pt.x < cx+45 && pt.y > cy-10 && pt.y < cy+10) color = PALE_RED; else color = PALE_BLUE; l.repaint (); } public void paint (Graphics g, JComponent c) { // Paint the view. super.paint (g, c); // Paint the brand. g.setColor (color); g.setFont (BRAND_FONT); int width = g.getFontMetrics ().stringWidth (MSG); int height = g.getFontMetrics ().getHeight (); g.drawString (MSG, (c.getWidth ()-width)/2, c.getHeight ()/2+height/4); } public void uninstallUI (JComponent c) { super.uninstallUI (c); ((JLayer) c).setLayerEventMask (0); } }; // Create a user interface to be decorated. JPanel pnlMain = new JPanel (); pnlMain.setLayout (new GridLayout (2, 1)); JPanel pnlTemp = new JPanel (); JLabel lblName = new JLabel ("Name:"); pnlTemp.add (lblName); JTextField txtName = new JTextField (20); pnlTemp.add (txtName); pnlMain.add (pnlTemp); pnlTemp = new JPanel (); JLabel lblAddr = new JLabel ("Address:"); pnlTemp.add (lblAddr); JTextField txtAddr = new JTextField (20); pnlTemp.add (txtAddr); pnlMain.add (pnlTemp); // Create the layer for the main panel using our custom layerUI. return new JLayer<JPanel> (pnlMain, layerUI); } private static void createAndShowUI () { JFrame frame = new JFrame ("Branded UI"); frame.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE); frame.add (createLayer ()); frame.pack (); frame.setLocationRelativeTo (null); frame.setVisible (true); } public static void main (String [] args) { Runnable r = new Runnable () { public void run () { createAndShowUI (); } }; EventQueue.invokeLater (r); } }
Listing 2 focuses on rendering a text-based brand over the UIbrands are useful for alerting users that they're only working with trial-based software. The brand text is translucent so that the background shows throughwe don't want the brand to be too intrusive.
On the other hand, we want to keep the users somewhat aware of the brandperhaps they'll decide to purchase their copies of the software. Listing 2 accomplishes this task by changing the brand color (to pale red) whenever the mouse is moved over the brand's initially pale blue text.