views:

99

answers:

3

I want to create two or more JButtons that share state, i.e. when the mouse button is pressed over either JButton, both are rendered as depressed (aka "armed"), or if they are checkboxes, both are checked/unchecked simultaneously etc.

To the user, it must appear as if both buttons were the same button, appearing in more than one place in the hierarchy (in reality Swing does not allow this.)

I can get half way there by creating a single ButtonModel and assigning the same model to both buttons. This synchronizes their armed/checked/selected states etc.

However, one noticeable effect that is not shared between buttons this way is focus - clicking on one button gives that button the focus (indicated by a rectangle within the button) and removes it from the other button. I would like to render both buttons as if they were focused whenever either button really has the focus.

Is there a clean way to do this?

Ideally I would like it to be independent of the chosen look-and-feel.

Edit: I've discovered another problem with sharing a ButtonModel. When one of the buttons loses focus, it sets the armed and pressed properties of the model to false. This happens after handling mousePressed, so if you press the second button when the first button has focus it does not enter the pressed state until you press it a second time.

+1  A: 

I'm assuming that you've already got text, listeners and the like sorted out.

Going to BasicButtonUI's paint method, we can see that it actually checks whether the button has focus before doing certain painting. So unless you can have two focussed components simultaneously, the only way to do it that I can think of is to use the other button's UI to paint.

Both buttons need to be a FocusButton, and need to call setButton on each other. I haven't bothered to add any null checking, amongst other things.

public class FocusButton extends JButton {
    private JButton btn;

    public FocusButton() {
        addFocusListener(new FocusListener() {
            public void focusGained(FocusEvent e) {
                // Other button seems to repaint when focus is gained anyway
            }

            public void focusLost(FocusEvent e) {
                btn.repaint();
            }
        });
    }

    public void setButton(JButton btn) {
        this.btn = btn;
    }

    public void paint(Graphics g) {
        if (!btn.hasFocus()) {
            super.paint(g);
        } else {
            btn.paint(g);
        }
    }
}

EDIT: This doesn't work too well if your buttons aren't the same size, and obviously doesn't work at all if they are supposed to have different text.

lins314159
Nice idea and has the potential to be the simplest solution if it can be extended to 3 or more buttons.
finnw
You could store a list of buttons instead of just one, change paint to use the method of whichever in your list is focussed, and repaint all on focus lost.
lins314159
+2  A: 

You made a really good move by using the same ButtonModel for the two buttons.

Now for your problem regarding focus. The answer is No. There is no L&F agnostic way. You have to override BasicButtonUI(or whichever ButtonUI you are using) and override the focus drawing logic.

Suraj Chandran
I think you are right and it is necessary to extend ButtonUI, but it's possible to delegate to the default UI. That's part of the technique I am using at the moment.
finnw
+3  A: 

Here's what I did:

  • Extend JButton with a new class SharedFocusButton
  • SharedFocusButton overrides hasFocus, getModel and paintBorder.
  • When either JButton.paintBorder(Graphics) or ButtonUI.update(Component, Graphics) is running, temporarily change the behaviour of hasFocus so that it returns true if any button in the group has the focus. Also temporarily change the behaviour of getModel to return a proxy ButtonModel (at other times it returns the shared ButtonModel)
  • The proxy ButtonModel behaves like the default, shared ButtonModel, except that it refuses to change the armed or pressed properties' values to false whilst handling a focusLost event.
  • Handle focusGained and focusLost, forcing all buttons in the group to redraw themselves (this won't happen automatically because each button has its own UI handling focus events.)

Demo

Remaining issues:
Focus traversal should probably be modified so that the Tab key never transfers focus from one button to another in the same group.

finnw
I think your solution would work better than mine since you're not restricted to buttons of the same size (hard to control if the user resizes your window) and appearance.
lins314159