Search code examples
javawindowsswingdatejava-5

Swing ignores first click after decreasing Windows time


I have a Swing application that deals with date and time, so a lot of tests are done changing the system's date and time settings. During the tests, we noticed that after decreasing the clock, the first click is ignored by the application.

Is it a bug of Swing/Java/Windows? Is there a workaround to this?

Interestingly, this issue only happens when decreasing the date/time settings. If I increase it, the application behaves normally.

Situation:

  • Swing application running.
  • Decrease Windows date and time settings (e.g. change time from 15:00 to 14:00).
  • Notice that the first click in the Swing application does not fire any action.

Code example (you can use it to testify the situation):

    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.awt.event.WindowAdapter;
    import java.awt.event.WindowEvent;

    import javax.swing.JButton;
    import javax.swing.JFrame;

    public class Main {

        public static void main(String[] args) {
            final JFrame frame = new JFrame("frame");
            final JButton button = new JButton("button");
            button.addActionListener(new ActionListener() {

                public void actionPerformed(final ActionEvent e) {
                    System.out.println("Button Pressed!");
                }
            });

            frame.add(button);
            frame.setSize(200, 200);
            frame.setVisible(true);
            frame.addWindowListener(new WindowAdapter() {

                @Override
                public void windowClosing(final WindowEvent e) {
                    System.exit(0);
                }
            });
        }

    }

Solution

  • I've debugged it via Eclipse and found out what is happening.

    • Clock at 15:00h.
    • Click at the button. Swing record last event time to 15:00.
    • Change the clock to 14:00h.
    • Click at the button. Swing ignores the event because it looks like a multi-click.

    The problem here is that the comparison made by Swing checking for multi-click is this:

    if (lastTime != -1 && currentTime - lastTime < multiClickThreshhold) {
        shouldDiscardRelease = true;
    

    Here, currentTime - lastTime yields a negative value. It's less than 0 (my multiClickThreshhold), so it does not fire the action event:

    public void mouseReleased(MouseEvent e) {
        if (SwingUtilities.isLeftMouseButton(e)) {
            // Support for multiClickThreshhold
            if (shouldDiscardRelease) {
                shouldDiscardRelease = false;
                return;
            }
            AbstractButton b = (AbstractButton) e.getSource();
            ButtonModel model = b.getModel();
            model.setPressed(false);
            model.setArmed(false);
        }
    }
    

    All the source listed above is in javax.swing.plaf.basic.BasicButtonListener.

    The Button class does have a setMultiClickThreshhold, but it throws IllegalArgumentException in case the threshhold is less than 0.

    So, as a workaround, I did this:

    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.awt.event.WindowAdapter;
    import java.awt.event.WindowEvent;
    import java.lang.reflect.Field;
    
    import javax.swing.AbstractButton;
    import javax.swing.JButton;
    import javax.swing.JFrame;
    
    public class Main {
    
        public static void main(String[] args) throws Exception {
            final JFrame frame = new JFrame("frame");
            final JButton button = new JButton("button");
            removeMulticlickThreshold(button);
    
            button.addActionListener(new ActionListener() {
    
                public void actionPerformed(final ActionEvent e) {
                    System.out.println("Button Pressed!");
                }
            });
    
            frame.add(button);
            frame.setSize(200, 200);
            frame.setVisible(true);
            frame.addWindowListener(new WindowAdapter() {
    
                @Override
                public void windowClosing(final WindowEvent e) {
                    System.exit(0);
                }
            });
        }
    
        private static void removeMulticlickThreshold(final JButton button) throws Exception {
            final Field multiClickThreshhold = AbstractButton.class.getDeclaredField("multiClickThreshhold");
            multiClickThreshhold.setAccessible(true);
            multiClickThreshhold.set(button, Long.MIN_VALUE);
        }
    
    }