<Swing Dev> Possible bug - buttons do not regain rollover state after reenabling

Alexander Potochkin Alexander.Potochkin at Sun.COM
Tue Jan 22 16:40:06 UTC 2008


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




More information about the swing-dev mailing list