Composing Swing Components: How do I add the ability to add ActionListeners?

I want to create a (simple, hopefully) custom Swing component by composing several existing components. In my case, it is an on-off switch which consists of a JLabel, and two JButtons for On and Off. I begin OnOffSwitch by extending JPanel. The constructor adds the sub-components, and sets itself up as an ActionListener for the buttons. The class has an isOn() method for querying the current state of the component.

I now want to add the ability to add ActionListeners to the OnOffSwitch class. I expected this functionality would come for free by having extended a Swing component like JPanel, but JPanel does not have this functionality. By the looks of the sources, every Swing component which does have this functionality re-implements it itself: the adding listeners to the list, the firing of ActionEvents, etc.

What is the correct way to achieve what I want? I can copy/paste that code from the various Swing components (or re-write the gist of it), or I can implement my own OnOffSwitchListener interface. To be consistent it seems that all my components should use ActionListeners.


Solution 1:

I'd use a JToggelButton, as shown here, or delegate to the contained buttons, as @duffymo suggests. If you really need a custom OnOffSwitchEvent, the standard wiring is outlined in EventListenerList, an instance of which is contained in every JComponent.

Addendum: Here's an example of delegating to a ButtonGroup containing two buttons. The label is decorated with a symbol, but any implementation of Icon is even more flexible.

BttonGroupTest iamge

import java.awt.Color;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.ButtonGroup;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JToggleButton;

/** @see https://stackoverflow.com/questions/6035834 */
public class ButtonGroupTest extends JComponent {

    private static final String ON = "On";
    private static final String OFF = "Off";
    private final JToggleButton bOn = new JToggleButton(ON);
    private final JToggleButton bOff = new JToggleButton(OFF);
    private final JLabel label = new JLabel(" \u2301 ");
    private final ButtonHandler handler = new ButtonHandler();

    public ButtonGroupTest() {
        this.setLayout(new FlowLayout());
        label.setOpaque(true);
        label.setBackground(Color.red);
        label.setFont(label.getFont().deriveFont(36f));
        ButtonGroup bg = new ButtonGroup();
        this.add(bOn);
        bg.add(bOn);
        bOn.setSelected(true);
        bOn.addActionListener(handler);
        this.add(label);
        this.add(bOff);
        bg.add(bOff);
        bOff.addActionListener(handler);
    }

    public void addActionListener(ActionListener listener) {
        bOn.addActionListener(listener);
        bOff.addActionListener(listener);
    }

    private class ButtonHandler implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            String cmd = e.getActionCommand();
            if (ON.equals(cmd)) {
                label.setBackground(Color.red);
            } else {
                label.setBackground(Color.black);
            }
        }
    }

    private void display() {
        JFrame f = new JFrame("ButtonGroupTest");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.add(this);
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                new ButtonGroupTest().display();
            }
        });
    }
}

Solution 2:

I personally don't think you need a custom Swing component. No need for your UI class to extend any Swing class; you aren't likely to provide much custom behavior. (I might concede for a JPanel that composes others.)

I would prefer composition over inheritance. Have a UI class that has Swing data members and give it methods to add and remove Listeners as you need them. You can change the behavior that way without having to rewrite the UI class. It's nothing more than a container.

public class MyUI
{
    private Button b = new Button();

    public void addButtonListener(ActionListener listener) { this.b.addActionListener(listener); }
}

Solution 3:

By the looks of the sources, every Swing component which does have this [ActionListener] functionality re-implements it itself: the adding listeners to the list, the firing of ActionEvents, etc.

Yep. That's what you have to do when you're writing a custom Swing component.

As you say, you can copy the ActionListener code from an existing Swing component,