How can I create my own component?

Tim Rohaly

To create an actual component that is lightweight requires six steps:

  • You need to define a constructor or constructors in order to initialize the state of your component If your component is to respond to any events internally (like mouse presses to activate it), this has to be enabled in the constructor, usually by adding the appropriate listener.
  • Assuming that in the previous step you added a listener for handling internal events, you have to now define the listener class. It is best to do this with an inner class because then access is restricted to the lightweight component.
  • You need to deal with sizing the object and programmatically changing any properties. For instance, if you have a button that supports changing of its label, you also need to provide a method to recalculate its size when the label is changed.
  • You need to override the paint() method so the component can be drawn. This is where you define the visual appearance of your component.
  • If you want your component to be non-rectangular, you need to override the contains() method. The contains() method should return true when it is over an area of the object. This routine should be very fast as it will be called frequently. By default, it always returns true. If your component is defined by a java.awt.Polygon shape, that class has a contains() method that you can call. Otherwise, you have to calculate this yourself.
  • If you plan on supporting the notification of objects when an event happens (like sending an java.awt.event.ActionEvent when a button is selected, you need to create add/removeXXXListener() methods. Assuming the event type you are using is one of the predefined AWT events, you can use the java.awt.AWTEventMulticaster class to add and remove listeners from the list.
And that's all there is to it.

The following source demonstrates all this by creating a round button component. This component is activated only if selected within the circle. The code is simplified to illustrate the important points when creating your own component - if you want this to behave like a real button then you will have to get more sophisticated with the mouse event handling. First, the constructor initializes the radius and creates a listener for mouse events:

import java.awt.*;
import java.awt.event.*;

public class CircularButton extends Component {

    private int radius;
    boolean pressed = false;
    ActionListener actionListener = null;


    public CircularButton() {
        this(5);
    }

    public CircularButton(int radius) {
        if (radius < 0) {
            throw new IllegalArgumentException("Illegal Radius: " + radius);
        }
        this.radius = radius;
        addMouseListener(new ClickAdapter());
        setSize(getPreferredSize());
    }

This component is the source of ActionEvents, like all buttons, so it is responsible for maintaining a list of listeners and providing methods to add and remove listeners. It does this with the help of the java.awt.AWTEventMulticaster class:
    public void addActionListener(ActionListener l) {
        actionListener = AWTEventMulticaster.add(actionListener, l);
    }

    public void removeActionListener(ActionListener l) {
        actionListener = AWTEventMulticaster.remove(actionListener, l);
    }

The constructor defined an inner class called ClickAdapter to handle mouse events. This event handler doesn't worry if the mouse click is over the object or not, it just keeps track of the state of pressed and notifies the listeners. Because the adding and removing of listeners is done with AWTEventMulticaster, invoking the notification method on the actionListener instance variable (which is actually an instance of AWTEventMulticaster) will notify all the listeners. (If you do not use AWTEventMulticaster, you would need to maintain the listener list in something like a java.util.Vector then iterate through the list of listener elements yourself, calling each listener method. And do it in a thread-safe manner, which AWTEventListener does for you.) ClickAdapter looks like this:

    private final class ClickAdapter extends MouseAdapter {
        public void mousePressed(MouseEvent e) {
            if (isEnabled()) {
                pressed = true;
                repaint();
            }
        }

        public void mouseReleased(MouseEvent e) {
            if (isEnabled()) {
                pressed = false;
                repaint();
                if (actionListener != null) {
                    actionListener.actionPerformed(
                        new ActionEvent(CircularButton.this,
                                        ActionEvent.ACTION_PERFORMED, ""));
                }
            }
        }
    }

There are three sizing routines that need to be overridden so that the layout managers can properly size and position this component:

    public Dimension getPreferredSize() {
        int side = radius *2;
        return new Dimension(side, side);
    }

    public Dimension getMinimumSize() {
        return getPreferredSize();
    }

    public Dimension getMaximumSize() {
        return getPreferredSize();
    }

To draw the component, you must override the paint() method. This is an important step because it defines the visual appearance of your component; an appearance that may even vary with the component's state, as does this button:

    public void paint(Graphics g) {
        super.paint(g);
        if (isEnabled()) {
            if (pressed) {
                g.setColor(Color.white);
                g.fillOval(0, 0, radius*2, radius*2);
                g.setColor(Color.black);
                g.drawOval(0, 0, radius*2, radius*2);
            }
            else {
                g.setColor(Color.black);
                g.fillOval(0, 0, radius*2, radius*2);
            }
        }
        else {
            g.setColor(Color.gray);
            g.fillOval(0, 0, radius*2, radius*2);
        }
    }

Next, override the contains() method in order to support a non-rectangular component. To check to see if a point is within the circle, compare against the radius. Note that the range of the x and y input parameters covers the full size of the circle, and nothing more.

    public boolean contains(int x, int y) {
        //distance = square root of (deltaX squared + deltaY squared)
        int deltax = x - radius;
        int deltay = y - radius;
        double distance = Math.sqrt(Math.pow(deltax, 2) + Math.pow(deltay, 2));
        return (distance <= radius);
    }

Finally, a main method is provided which simply creates a frame with several instances of CircularButton for test purposes:

    public static void main(String[] args) {
        final CircularButton cb1, cb2, cb3, cb4, cb5;
        Frame f = new Frame();
        f.addWindowListener(
            new WindowAdapter() {
                public void windowClosing(WindowEvent e) {
                    System.exit(0);
                }
            }
        );
        f.setLayout(new FlowLayout());
        f.add(cb1 = new CircularButton(25));
        f.add(cb2 = new CircularButton(25));
        f.add(cb3 = new CircularButton(25));
        f.add(cb4 = new CircularButton(25));
        f.add(cb5 = new CircularButton(25));
        ActionListener al = new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                Object o = e.getSource();
                String msg;
                if (o.equals(cb1)) {
                    msg = "One";
                }
                else if (o == cb2) {
                    msg = "Two";
                }
                else if (o == cb3) {
                    msg = "Three";
                }
                else if (o == cb4) {
                    msg = "Four";
                }
                else if (o == cb5) {
                    msg = "Five";
                }
                else {
                    msg = "Huh?" + o;
                }
                System.out.println("Button: " + msg);
            }
        };
        cb1.addActionListener(al);
        cb2.addActionListener(al);
        cb3.addActionListener(al);
        cb4.addActionListener(al);
        cb5.addActionListener(al);
        cb5.setEnabled(false);
        f.pack();
        f.show();
    }
}

0 Comments  (click to add your comment)
Comment and Contribute

 

 

 

 

 


(Maximum characters: 1200). You have 1200 characters left.

 

 

About | Sitemap | Contact