How to wait for radio button to get selected in lost focus event

1.8k Views Asked by At

I have a Swing program that does a search based on the contents of some text fields and settings of a pair of radio buttons (in a button group). The program will automatically search when certain of the text fields lose focus. The problem comes in when the lose focus event is triggered by a click on one of the radio buttons. The lost focus event on the text field is getting processed before the radio button isSelected() values have changed, so the search is done with the "wrong" (i.e. old) parameters, instead of the parameters based on the new setting of the radio buttons.

I tried invoking the search using my own invokeWhenIdle method (shown below) to run the search after the event queue had settled down, but it still is using the old setting of the radio buttons.

My only working solution is to delay for 250 milliseconds in the lost focus event before running the search, so that the radio buttons have time to change. This works, but it makes the UI seem sluggish.

Any better ideas?

public static void invokeWhenIdle(final int a_max_retry, final Runnable a_runnable) {
  if (a_max_retry <= 0) {
    throw new IllegalStateException("invokeWhenIdle: Could not run " + a_runnable);
  }

  // get the next event on the queue
  EventQueue l_queue = Toolkit.getDefaultToolkit().getSystemEventQueue();
  AWTEvent l_evt = l_queue.peekEvent();
  if (l_evt == null) {
    // nothing left on the queue (but us), we can do it
    SwingUtilities.invokeLater(a_runnable);
  } else {
    // still something in the queue, try again
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        invokeWhenIdle(a_max_retry - 1, a_runnable);
      }
    });
  }
}
1

There are 1 best solutions below

1
On

Not an answer, but an explanation about what is happening. Maybe it will spark an idea...

The problem is that a mousePressed arms the button model and the mouseReleased actually changes the selected value of the model.

When you execute the FocusListener code the radio button the model is in an undefined state. Even if you add the FocusListener code to the end of the EDT by using invokeLater the code will still execute before the mouseReleased event is generated.

The following shows how you might code the listener to handle this. It assumes the state of the button is about to change:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class FocusSSCCE extends JPanel
{
    public FocusSSCCE()
    {
        final JRadioButton radio = new JRadioButton("Radio");
        add( radio );
        radio.setMnemonic('R');

        JTextField textField = new JTextField(10);
        add( textField );

        JButton button = new JButton("Button");
        add( button );

        textField.addFocusListener( new FocusAdapter()
        {
            public void focusLost(FocusEvent e)
            {
                boolean isSelected = radio.isSelected();

                //  Assumes selected state will change

                if (radio.getModel().isArmed())
                    isSelected = !isSelected;

                System.out.println( isSelected );
            }
        });
    }

    private static void createAndShowUI()
    {
        JFrame frame = new JFrame("FocusSSCCE");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add( new FocusSSCCE() );
        frame.pack();
        frame.setLocationRelativeTo( null );
        frame.setVisible( true );
    }

    public static void main(String[] args)
    {
        EventQueue.invokeLater(new Runnable()
        {
            public void run()
            {
                createAndShowUI();
            }
        });
    }
}

However, even this approach can't be guaranteed to work. If for some reason the user generates the mousePressed event on the radio button and them moves the mouse away from the radio button before releasing the mouse, then the selected state of the radio button is not changed.

Similiarly, even your original implementation to sleep for 250ms can not be guaranteed to work because the user could theoretically hold down the mouse for more than 250ms which would also generate the wrong value.

My workaround for this was to make the radio buttons non focusable

I can't think of any better approach.

Edit:

I just thought of a wild solution.

textField.addFocusListener( new FocusAdapter()
{
    public void focusLost(FocusEvent e)
    {
        if (e.getOppositeComponent() instanceof JRadioButton)
        {
            final JRadioButton radio = (JRadioButton)e.getOppositeComponent();

            MouseListener ml = new MouseAdapter()
            {
                public void mouseReleased(MouseEvent e)
                {
                    System.out.println( radio.isSelected() );
                    radio.removeMouseListener(this);
                }
            };

            radio.addMouseListener( ml );
        }

        else
            System.out.println( radio.isSelected() );
    }
});

Basically your processing code won't execute until the mouse has been released when you click on the radio button.