ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Using the Decorator Pattern
Pages: 1, 2

The constructor assigns the decorated component to child and adds it as a child component of the decorator. Note that here we use a BorderLayout as the layout for the decorator. This means the added JComponent will occupy the whole area of the decorator.



Now, take a look at the paint method. It first calls the parent class' paint method. This will paint the decorator as well as all of its children. We then draw a rectangle around the decorator area, after first getting the height and the width of the decorator.

Figure 1 shows a JFrame with three components:

  • An instance of JBorderLabel.
  • A decorated JLabel.
  • A decorated JCheckBox.


Figure 1 -- comparing subclassing and the Decorator pattern

As Figure 1 shows, there is no difference in appearance between an instance of JBorderLabel and a decorated JLabel. This demonstrates that the Decorator pattern can be used as an alternative to subclassing. The third component proves that you can use the same Decorator to extend the behavior of an instance of a different class. In this sense, the decorator is superior, because you only need to create one class (BorderDecorator) to extend the functionality of objects of many different types.

Listing 3 gives the the code of the JFrame in Figure 1.

Listing 3 -- using the BorderDecorator class

package decorator;

import java.awt.*;
import javax.swing.*;
import java.awt.event.*;

public class Frame1 extends JFrame {

   JBorderLabel label1 =
      new JBorderLabel("JLabel Subclass");

   BorderDecorator label2 =
      new BorderDecorator(new JLabel("Decorated JLabel"));

   BorderDecorator checkBox1 =
      new BorderDecorator(new JCheckBox("Decorated JCheckBox"));

   public Frame1() {
      try {
         this.setDefaultCloseOperation(EXIT_ON_CLOSE);
         getContentPane().setLayout(null);
         label1.setBounds(new Rectangle(10, 10, 120, 25));
         this.getContentPane().add(label1, null);
         label2.setBounds(new Rectangle(10, 60, 120, 25));
	     this.getContentPane().add(label2, null);
         checkBox1.setBounds(new Rectangle(10, 110, 160, 25));
         this.getContentPane().add(checkBox1, null);
      }
      catch(Exception e) {
         e.printStackTrace();
      }
   }

   public static void main(String[] args) {
      Frame1 frame1 = new Frame1();
      frame1.setBounds(0, 0, 200, 200);
      frame1.setVisible(true);
   }
}

The ResizableDecorator Class

The ResizableDecorator is a different type of decorator. It does not override the paint method of its parent class; instead, it adds a small button that, when clicked, resizes and restores itself. Listing 4 presents the code of the ResizableDecorator class.

Listing 4 -- the ResizableDecorator class

package decorator;

import javax.swing.*;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.Rectangle;

public class ResizableDecorator extends JComponent {

   // decorated component
   protected JComponent child;
   private JButton button = new JButton();
   boolean minimum = false;
   private Rectangle r;

   public ResizableDecorator(JComponent component) {
      child = component;
      this.setLayout(new BorderLayout());
      this.add(child);
      child.setLayout(null);
      button.setBounds(0, 0, 8, 8);
      child.add(button);
      button.addActionListener(new java.awt.event.ActionListener() {
         public void actionPerformed(ActionEvent e) {
            button_actionPerformed(e);
         }
      });
   }

   void button_actionPerformed(ActionEvent e) {
      if (minimum) {
         this.setBounds(r);
      }
      else {
         r = this.getBounds();
         this.setBounds(r.x, r.y, 8, 8);
      }
      minimum = !minimum;
   }
}

First note that this decorator also extends JComponent, and its constructor accepts a JComponent. In addition to a variable called child to reference the decorated component, it also declares a JButton.

The first three lines of its constructor add the decorated component as the child component of the decorator.

child = component;
this.setLayout(new BorderLayout());
this.add(child);

It then adds the JButton as a child component of the decorated component. The child's layout is null. The JButton will have a dimension of 8 x 8 pixels and is added to the left-hand corner of the decorated component.

child.setLayout(null);
button.setBounds(0, 0, 8, 8);
child.add(button);

Finally, it adds a listener to the JButton.

button.addActionListener(new java.awt.event.ActionListener() {
   public void actionPerformed(ActionEvent e) {
      button_actionPerformed(e);
   }
});

When added to a container, the component decorated using ResizableDecorator will have a small button on its left-hand corner. When clicked, this button will execute the code inside of the button_actionPerfomed method. First, the method checks the value of minimum. This boolean indicates whether or not the decorator size is at its minimum. Initially, the value is false, so the else block is executed.

else {
   r = this.getBounds();
   this.setBounds(r.x, r.y, 8, 8);
}

It assigns the current dimension to the Rectangle r and sets the bounds to 8 x 8, with the left-hand corner remaining at the current position. Then, it toggles the value of minimum.

minimum = !minimum;

When the JButton is clicked for the second time, the value of minimum is true. This time, the if block is executed:

if (minimum) {
   this.setBounds(r);
}

The code will restore the decorator to its previous size.

Figure 2 shows a JFrame with three decorated components, and its code is given in Listing 5.


Figure 2 -- three components decorated using ResizableDecorator

Listing 5 -- using the ResizableDecorator class

package decorator;

import java.awt.*;
import javax.swing.*;
import java.awt.event.*;

public class Frame2 extends JFrame {
   ResizableDecorator label1 =
      new ResizableDecorator(new JLabel(" Label1"));
   ResizableDecorator button1 =
      new ResizableDecorator(new JButton("Button"));
   BorderDecorator label2 =
      new BorderDecorator(new ResizableDecorator(
         new JLabel(" doubly decorated")
   ));

   public Frame2() {
      try {
         jbInit();
      }
      catch(Exception e) {
         e.printStackTrace();
      }
   }

   public static void main(String[] args) {
      Frame2 frame = new Frame2();
      frame.setBounds(0, 0, 200, 200);
      frame.setVisible(true);
   }

   private void jbInit() throws Exception {
      this.setDefaultCloseOperation(EXIT_ON_CLOSE);
      this.getContentPane().setLayout(null);
      label1.setBounds(new Rectangle(10, 10, 120, 25));
      label2.setBounds(new Rectangle(10, 60, 120, 25));
      button1.setBounds(new Rectangle(10, 110, 120, 25));
      this.getContentPane().add(label1, null);
      this.getContentPane().add(label2, null);
      this.getContentPane().add(button1, null);
   }
}

Note that you can decorate a JComponent using one or more decorators, just like the JLabel label2 in Listing 5. Also, if you need to work with many decorators, you may want to consider having an abstract Decorator class from which all decorator classes derive. This way, you can put the common methods in the abstract class.

Summary

This article has compared subclassing with the Decorator pattern, giving two examples of decorators in Swing. While the use of the Decorator pattern is very common to change the visual look of a Swing component, its use is not limited to changing the user interface.

Budi Kurniawan is a senior J2EE architect and author.


Return to ONJava.com.