Sharing state between JButtons
Question
I want to create two or more JButton
s 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.
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 classSharedFocusButton
SharedFocusButton
overrideshasFocus
,getModel
andpaintBorder
.- When either
JButton.paintBorder(Graphics)
orButtonUI.update(Component, Graphics)
is running, temporarily change the behaviour ofhasFocus
so that it returnstrue
if any button in the group has the focus. Also temporarily change the behaviour ofgetModel
to return a proxyButtonModel
(at other times it returns the sharedButtonModel
) - The proxy
ButtonModel
behaves like the default, sharedButtonModel
, except that it refuses to change thearmed
orpressed
properties' values tofalse
whilst handling afocusLost
event. - Handle
focusGained
andfocusLost
, 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.