Question

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.

Was it helpful?

Solution

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.

OTHER TIPS

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.)

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.

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top