1

I would like to create a new Swing JComponent based on an existing one, but with a different API. In other words, I don't want to extend the existing component, because I don't want it's API to be accessible.

Here an example to clarify my needs:

A replacement of the JCheckBox which show two buttons ON/OFF. This could be based on a pre-configured JCommandButtonStrip (some info here) but exposing exactly the same API of JCheckBox. The configuration of the JCommandButtonStrip must not be altered.

What is the best approach for such a problem?

Clarifications:

As someone pointed out, what I wrote about API is not clear.

Of course JComponent have a number of public fields and methods which will be available for each sub-class. Then each sub-class of JComponent may add its own public fields and methods. For example, AbstractButton adds the isSelected() method, while JCommandButtonStrip adds the getButtonCount() method.

So, what I meant is: I want to create a new JComponent sub-class MyJComponent, which is based on an existing one ExistingJComponent. I don't want the public methods of ExistingJComponent, except those of JComponent, to be exposed by my class MyJComponent. Then I want to add some public methods to MyJComponent.

Please note that I'm not looking for an alternative to the JCommandButtonStrip/JCheckBox example. I'm interested in a general approach to such a problem.

Paolo Fulgoni
  • 5,208
  • 3
  • 39
  • 55
  • 1
    you can't reduce the scope of methods, so once a method in ExistingComponent is public it needs to be public in MyComponent as well (and comply to its contract!). So the answer is: don't waste your time on impossibilities :-) – kleopatra Mar 26 '14 at 09:33
  • @kleopatra your comment would be correct if `MyComponent` extended `ExistingComponent`. But that's not what I asked. `MyComponent` shall **encapsulate** `ExistingComponent`. – Paolo Fulgoni Mar 26 '14 at 10:19
  • 1
    I think either @Charlie's answer,@trahsgod's example [shown here](http://stackoverflow.com/questions/6035834/composing-swing-components-how-do-i-add-the-ability-to-add-actionlisteners/6036048#6036048) and my own example cover exactly what you're looking for. – dic19 Mar 26 '14 at 10:25
  • 2
    okay, so I misunderstood the _is based_ - then one answer is to extend JComponent, c&p most of ExistingComponent (and its ui delegate, one for every LAF that needs to be supported) and hook the new component into the LAFs .. But why in frozen hell would you want to waste time in re-inventing a well-designed and well-tested wheel? – kleopatra Mar 26 '14 at 11:19
  • @kleopatra c&p is something that I really want to avoid. I don't want to re-invent the wheel, I want to customize it. – Paolo Fulgoni Mar 28 '14 at 10:45
  • _I want to customize_ that's no reason for hiding (inherited) public api – kleopatra Mar 28 '14 at 10:53

4 Answers4

3

You can create a new class which extends JComponent then inside the constructor insert a checkbox into itself.

public class MyCoolCheckbox extends JComponent{
    private JCheckBox checkbox;
    public MyCoolCheckbox(String label) {
        checkbox= new JCheckBox(label);
        this.setLayout(new BorderLayout());
        this.add(checkbox, BorderLayout.CENTER);
    }
}

This is obviously incomplete and you may need to delegate certain methods to the child. It might get messy. IDEs like IntelliJ IDEA will generate all this for you if you hit alt-ins (by default) then delegate, then select the checkbox member and pick the entries you want to delegate. For example:

public void setForeground(Color fg) {
    checkbox.setForeground(fg);
}

public void setBackground(Color bg) {
    checkbox.setBackground(bg);
}
public Color getForeground() {
    return checkbox.getForeground();
}

public Color getBackground() {
    return checkbox.getBackground();
}

Keep in mind that because the child is within the Swing component tree, other code will have access to the children even though they are marked private.

((JCheckBox)myCoolCheckbox.getComponents()[0]).setSelected(true);
Charlie
  • 8,530
  • 2
  • 55
  • 53
  • Could you elaborate your answer? What shall I do with the checkbox? – Paolo Fulgoni Mar 25 '14 at 17:11
  • Do with it what you want, you indicated you wanted to wrap the checkbox component but not expose it's API. – Charlie Mar 25 '14 at 21:16
  • Thanks @Charlie, that's much more clear now! But which methods shall I forward to the delegate? – Paolo Fulgoni Mar 25 '14 at 21:45
  • It depends on what functionality of the contained checkbox you want to expose. If you want a consumer of your control to be able to change the text, you will need to delegate the setText method. You would probably also want to delegate all the correlating get methods. – Charlie Mar 25 '14 at 21:48
  • Shall I also forward any `JComponent`'s method? – Paolo Fulgoni Mar 25 '14 at 22:24
  • Any that you think are important. The ones related to size might be useful. – Charlie Mar 26 '14 at 04:07
3

As shown here, you can use two instances of JToggleButton in a ButtonGroup to "show two buttons ON / OFF." The ButtonGroup causes only one button in the group to be selected at a time. The following change is illustrated:

private final JLabel label = new JLabel(" \u2713 ");

image

Community
  • 1
  • 1
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • The three answer are quite different, i think i didn't understand the question. – nachokk Mar 25 '14 at 19:45
  • I've just edited the question, hope it's now more clear – Paolo Fulgoni Mar 26 '14 at 08:11
  • Sadly, no; to the extent that inheritance breaks encapsulation, I'm not sure of a "general approach to such a problem," except via delegation; this answer really only addresses the "two buttons" aspect of your question. – trashgod Mar 26 '14 at 13:13
1

Based on this picture of JCommandButtonStrip:

enter image description here

I think you are looking for JToggleButton as @trashgod suggested, but I'm not sure about buttons group given the current description of your "problem". If you need buttons group then use it.

Anyway my answer points to this line:

This could be based on a pre-configured JCommandButtonStrip (some info here) but exposing exactly the same API of JCheckBox.

Once again it's not clear if you're trying to do a buttons bar such as JCommandButtonStrip or you want to do something else. However you can make your own component extending from JComponent and delegate only those methods that are needed from the outside. For example let's say you want to do a buttons bar such as JCommandButtonStrip. Then you can have:

  • One class extending from JComponent: your buttons bar.
  • Another one providing an API to add "commands" to the buttons bar.

Note: There's already a JToolBar component which can perfectly be used without reinvent the wheel. The example below is just to show you that you can control the API offered to the developers.

MyCommandBar.java

import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.event.ActionListener;
import java.util.HashMap;
import java.util.Map;
import javax.swing.AbstractButton;
import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JToggleButton;
import javax.swing.event.ChangeListener;

public class MyCommandBar extends JComponent {

    private final JPanel content;
    private final Map<String, CommandItem> map = new HashMap<>();

    public MyCommandBar() {
        super();
        content = new JPanel(new GridLayout(1, 0));
        content.setOpaque(false);
        setLayout(new FlowLayout());
        add(content);
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);

        Graphics graphics = g.create();
        graphics.setColor(getBackground());
        graphics.fillRect(0, 0, getWidth(), getHeight());
        graphics.dispose();
    }

    public void addCommandItem(String actionCommand, CommandItem commandItem) {
        if(map.get(actionCommand) != null) {
            removeCommandItem(actionCommand);
        }
        content.add(commandItem.getComponent());
        map.put(actionCommand, commandItem);
    }

    public void removeCommandItem(String actionCommand) {
        CommandItem commandItem = map.get(actionCommand);
        if(commandItem != null) {
            content.remove(commandItem.getComponent());
            content.revalidate();
            content.repaint();
            map.remove(actionCommand);
        }
    }

    public CommandItem getCommandItem(String actionCommand) {
        return map.get(actionCommand);
    }

    public static class CommandItem {

        public static final int TOGGLE_BUTTON_STYLE = 0;
        public static final int CHECK_BOX_STYLE = 1;
        public static final int DEFAULT_BUTTON_STYLE = 2;

        private final AbstractButton component;

        public CommandItem(String text, boolean state, Icon icon, int style) {
            switch(style) {
                case TOGGLE_BUTTON_STYLE : component = new JToggleButton(text, icon, state); break;
                case CHECK_BOX_STYLE : component = new JCheckBox(text, icon, state); break;
                    default: component = new JButton(text, icon);
            }
        }

        protected AbstractButton getComponent() {
            return component;
        }

        public void addActionListener(ActionListener listener) {
            component.addActionListener(listener);
        }

        public void addChangeListener(ChangeListener listener) {
            component.addChangeListener(listener);
        }

        public void setAction(Action action) {
            component.setAction(action);
        }
    }
}

Example of use

This code snippet shows how MyCommandBar class should be used:

MyCommandBar commandBar = new MyCommandBar();
commandBar.setBorder(BorderFactory.createLineBorder(Color.black, 1));
commandBar.addCommandItem("BOLD", new MyCommandBar.CommandItem("<html><b>Bold</b></html>", true, null, MyCommandBar.CommandItem.TOGGLE_BUTTON_STYLE));
commandBar.addCommandItem("ITALICS", new MyCommandBar.CommandItem("<html><i>Italics</i></html>", false, null, MyCommandBar.CommandItem.CHECK_BOX_STYLE));
commandBar.addCommandItem("UNDERLINE", new MyCommandBar.CommandItem("<html><u>Underline</u></html>", false, null, MyCommandBar.CommandItem.DEFAULT_BUTTON_STYLE));

And you'll see something like this:

enter image description here

dic19
  • 17,821
  • 6
  • 40
  • 69
1

You can create MyJComponent subclass of JComponent with a private field that references a forwarding class for ExistingComponent.
The interactions with ExistingComponent are done with the forwarding class through methods of MyJComponent, and you are free to add more methods to MyJComponent.
Please see Effective Java item 16, for the delegation pattern used with the forwarding class.

dawww
  • 371
  • 1
  • 4
  • 16
  • 1
    This sounds exactly like my answer. – Charlie Mar 26 '14 at 22:05
  • 1
    @Charlie: it sounds a bit different to me. If I understood correctly @dawww's answer, he proposes to forward all the `JComponent` public methods from `MyJComponent` to the delegate `ExistingComponent`. While you suggested to add a `ExistingComponent` as a child of the `MyJComponent` container and forward only the methods "_that you think are important_". – Paolo Fulgoni Mar 28 '14 at 10:35
  • So the difference is all vs important? You can't forward all, for example the Component.getParent() and JComponent.setUI() methods. You probably wouldn't want to waste time delegating all the methods either, JComponent.getInheritsPopupMenu() or any of the deprecated methods. – Charlie Mar 28 '14 at 16:45