Hello Martin
Your observation is correct,
Swing doesn't count a button as rollovered
if it was disabled and enabled again
(actually I haven't seen a GUI library
which is ready for such situations)
This problem very rare affects anybody
so I'd see it as a Swing limitation
(however limitation is a bit too strong word for this case)
While it is definitely possible to fix this bug
I doubt it worth doing, because in this case
we'll have to fix tons of other cases when a component
was moved/disabled/hidden and shown again
which is not reasonable
Here is a workaround for your test case:
private void disableButtonTemporarily() {
button.setEnabled(false);
Timer timer = new Timer(1000, new ActionListener() {
public void actionPerformed(ActionEvent e) {
button.setEnabled(true);
Point l = MouseInfo.getPointerInfo().getLocation();
Point buttonLocationToScreen = new Point();
SwingUtilities.convertPointToScreen(buttonLocationToScreen, button);
if (new Rectangle(buttonLocationToScreen,
button.getSize()).contains(l)) {
button.getModel().setRollover(true);
}
}
});
timer.setRepeats(false);
timer.start();
}
Thanks
alexp
Hi,
Sorry to intrude without a proper introduction. I just want some insight
before reporting a possible bug.
A friend of mine asked me to help him with a problem he has with a swing
app. In this app some buttons are disabled for a short period of time,
after which, if the user has not moved the mouse, the button doesn't
show the rollover state after becoming enabled again. I tested this on
Java6 update 3, using this quick&dirty test:
------------
import java.awt.event.*;
import javax.swing.*;
public class RolloverTest extends JFrame {
private JButton button;
public static void main(final String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new RolloverTest();
}
});
}
public RolloverTest() {
super("Rollover Test");
createButton();
setDefaultCloseOperation(EXIT_ON_CLOSE);
pack();
setVisible(true);
}
private void createButton() {
button = new JButton("Press me");
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
disableButtonTemporarily();
}
});
add(button);
}
private void disableButtonTemporarily() {
button.setEnabled(false);
Timer timer = new Timer(1000, new ActionListener() {
public void actionPerformed(ActionEvent e) {
button.setEnabled(true);
}
});
timer.setRepeats(false);
timer.start();
}
}
-----------
Sorry I couldn't test it on the latest codebase, I don't have an
environment set up for that purpose. Please check if you can that this
also happens with later versions of Swing or the JDK.
Also, I did some digging in the source at openjdk to see what could be
the problem. Since it happens not only with JButton, but also to
JMenuItem, JCheckBox, etc, at first I thought that the problem might be
in the DefaultButtonModel, that the flag was simply lost when the button
is disabled. But apparently DefaultButtonModel.setEnabled() unsets other
flags but not the rollover state, although
DefaultButtonModel.setRollover() prevents changes to the rollover flag
if the button is disabled. In consequence, I couldn't manage a
workaround by providing a different ButtonModel.
It seems that AbstractButton.setEnabled() forces the rollover state to
change before disabling the button. There's no comment on the source as
to why this state change is handled by AbstractButton and not by the
ButtonModel, as happens with the other states. Is there a reason why
AbstractButton.setEnabled() has to handle the rollover and not
ButtonModel.setEnabled()? It seems that this is due to the order of how
event propagation is to occur, but I'm uncertain.
----------
setEnabled in AbstractButton:
public void setEnabled(boolean b) {
if (!b && model.isRollover()) {
model.setRollover(false);
}
super.setEnabled(b);
model.setEnabled(b);
}
setEnabled in DefaultButtonModel:
public void setEnabled(boolean b) {
if(isEnabled() == b) {
return;
}
if (b) {
stateMask |= ENABLED;
} else {
stateMask &= ~ENABLED;
// unarm and unpress, just in case
stateMask &= ~ARMED;
stateMask &= ~PRESSED;
}
fireStateChanged();
}
----------
If AbstractButton didn't force the rollover to false, and let the
ModelButton handle this, an implementation of ModelButton could store
the right state of the rollover even though it would be superseded by
the enabled state ( i.e. although rollover is true, it would be shown as
false if enabled is false). Therefore, when the button is reenabled, the
rollover could be restored as expected. But that's only if there isn't a
special reason why AbstractButton has to handle the rollover state
before disabling the button.
That's all. Sorry for the long mail.
Best Regards,
Martin Alterisio