Java Tips Weblog

  • Blog Stats

    • 1,258,383 hits
  • Categories

  • Archives

Dialog Focus

Posted by Rob Camick on March 14, 2010

When a JDialog (or JFrame for that matter) is made visible, focus is placed on the first focusable component by default. There may be times when you want to change this behaviour. The obvious solution would be to invoke the requestFocusInWindow() method on the component you wish to receive focus. The problem is that this doesn’t work all the time.

Lets take a simple panel with two components and you want focus on the second component. The code might look something like:

JTextField textField = new JTextField(10);
JButton button = new JButton( "Button" );
JPanel panel = new JPanel();
panel.add( textField );
panel.add( button );
button.requestFocusInWindow();

The problem is this code won’t work because a component can’t request focus unless it has been added to a “realized” dialog. A realized dialog basically means that the Swing JDialog has been added to a peer component that represents a dialog on the underlying OS. This occurs when you invoke the pack() or setVisible( true ) methods on the JDialog.

So the simple solution to this problem is to invoke the requestFocusInWindow() method after packing the dialog:

JDialog dialog = new JDialog();
dialog.add( panel );
dialog.pack();
button.requestFocusInWindow();
dialog.setVisible( true );

Optionally, the requestFocusInWindow() method could be placed after the setVisible(…) method. However, remember that when using a modal dialog this will not work since the statement won’t be executed until after the dialog is closed.

The above solution works in many cases, but does have a drawback. You may not always have easy access to the component you wish to gain focus. An example of this situation would be when you use one of the JOptionPane.showXXX(…) methods. These methods allow you to construct a functional modal dialog with custom buttons simply by creating a panel and by invoking the appropriate convenience showXXX method. A simple example of creating a dialog with a contained panel and “Yes”, “No”, “Cancel” buttons would be:

JTextField userid = new JTextField(10);
JTextField password = new JTextField(10);
JPanel panel = new JPanel(new GridLayout(2,2));
panel.add( new JLabel( "Userid:") );
panel.add( userid );
panel.add( new JLabel( "Password:") );
panel.add( password );
JOptionPane.showConfirmDialog(null, panel);

In this case, if you run the above code you will notice that focus is on the “Yes” button. I suspect in most cases you would want focus on the “Userid:” text field. However in this case we don’t have access to the dialog that is created so we can’t use the approach from above to request focus on the text field after the dialog has been packed.

One solution for this problem is to use an AncestorListener. According to the AncestorListener API an event is fired when the component becomes visible or invisible, either by the setVisible() method or by being added or removed from the component hierarchy. For our purposes this means that the event is fired when the dialog containing the component is made visible, not when the component is added to the panel.

To take advantage of the AncestorListener generated events I created the RequestFocusListener class. This class simply handles the ancestorAdded event and invokes the requestFocusInWindow() method on the component. To use the RequestFocusListener class in the above example the code would be changed to:

JTextField userid = new JTextField(10);
userid.addAncestorListener( new RequestFocusListener() );

It doesn’t matter what panel the text field is added to, or what dialog the panel is added to, the text field will recieve focus. Also, this approach will work if the panel is added to a visible dialog, since the ancestorAdded event will still be generated.

All the suggestion made here will apply to JFrames as well as dialogs. Hopefully they will help resolve any focus issues you have related to initial focus when a dialog or frame is displayed.

Get The Code

RequestFocusListener.java

Related Reading

Java Tutorials: How to Make Dialogs
Java API: AncestorListener

About these ads

34 Responses to “Dialog Focus”

  1. André Uhres said

    Thanks, works fine and is very useful for me.

  2. Leon said

    thanks a lot.. I searched everywhere, asking in the forums, but only here I get the solution.

  3. André Uhres said

    Just a hint: instead of AncestorListener we can also use HierarchyListener with something like this in hierarchyChanged:

    
    if ((HierarchyEvent.SHOWING_CHANGED & e.getChangeFlags()) != 0
    &&  component.isShowing()) 
    {
        component.requestFocusInWindow();
        component.removeHierarchyListener(this);
    }
    
  4. Jim Morris said

    FYI as many reported this does not work under jre6 and Linux, however putting an invokeLater in makes it work, I think it is a timing issue, so this is not a guaranteed fix.

    
    public class RequestFocusListener implements AncestorListener
    {
        public void ancestorAdded(final AncestorEvent e)
        {
            final AncestorListener al= this;
            SwingUtilities.invokeLater(new Runnable(){
    
                @Override
                public void run()
                {
                    JComponent component = (JComponent)e.getComponent();
                    component.requestFocusInWindow();
                    component.removeAncestorListener( al );
                }
            });
        }
    
        public void ancestorMoved(AncestorEvent e) {}
        public void ancestorRemoved(AncestorEvent e) {}
    }
    
  5. Neither AncestorListener nor HierarchyListener works on Linux jre6 (tried with and without SwingUtilities.invokeLater

    • Anonymous said

      For me, it also did not work, even with the ‘invokeLater()’. For me ‘HierarchyListener’ with an extra 100ms delay seems to work reliably on Linux as well as Windows.

  6. What is the purpose of removing the listener (in RequestFocusListener) after 1st invocation? I am using it to grab focus to fields in complex GUI components shown in JOptionPanes. When re-using the GUI component, I found the need to comment out that line.

    • Rob Camick said

      I guess I was just thinking it was cleaner to remove the listener after it served its purpose. In most cases I guessed that if people reused a panel they would create new instance of the panel, rather than reset every component back to a default state. Therefore I assumed that using the listener would be part of the construction code of the panel. Commenting out the code should cause no problems if you want that for the default hehaviour. I must admit I have also thought about adding a “remove listener” property, so you can control this behaviour.

  7. Anonymous said

    This worked great, except, shouldn’t e.getComponent() be e.getSource()?

    • Rob Camick said

      Actually it should be:

      JComponent component = e.getComponent();
      

      The getComponent() method does the cast for you and returns a JComponent so we might as well take advantage of the convenience method.

      • Anonymous said

        Oh, my mistake. I was looking at the docs before and could see getSource(), but not getComponent(). I suspect I was looking at the docs for EventObject though, rather than AncestorEvent.
        Carry on!

  8. Anonymous said

    Thanks, works well..

  9. Anonymous said

    None of these suggestions work for me, using JOptionPane on Linux and jre6. I have found a way however, by doing what the javadoc for JOptionPane calls “direct use” and constructing the dialog manually.

    So, instead of using one of the static showXxxxxDialog() methods, you create an instance of JOptionPane and then use the createDialog() method on the pane to expose the enclosing dialog. You can then add a WindowFocusListener to this dialog window and in the listener’s windowGainedFocus() method request focus for your component in the dialog.

  10. Prasad said

    Hello everyone, this is prasad. I managed to set the focus on both windows and linux with the following code. In the following code the focus is set on xField,

    JTextField xField = new JTextField();
    JTextField yfield = new JTextField();
    xField.addHierarchyListener(new HierarchyListener() 
    {
        public void hierarchyChanged(HierarchyEvent e) 
        {
            final Component c = e.getComponent();
            if (c.isShowing() && (e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0) 
            {
                Window toplevel = SwingUtilities.getWindowAncestor(c);
                toplevel.addWindowFocusListener(new WindowAdapter() 
                {
                    public void windowGainedFocus(WindowEvent e) 
                    {
                        c.requestFocus();
                    }
                });
            }
        }
    });
    
    Object[] detail = new Object[]{"x: ", xField, "", "", "                   OR", "", "", "y: ", yField};
    int optionType = JOptionPane.showConfirmDialog(this, detail, "", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);
    
    • Ilja said

      Many thanks. It’s works. (java version “1.6.0_30″, linux)

      
      public class RequestFocusListener implements HierarchyListener 
      {
      	public RequestFocusListener() {}
      
      	@Override
      	public void hierarchyChanged(HierarchyEvent e) 
      	{
      		final Component c = e.getComponent();
      		if (c.isShowing() && (e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0) 
      		{
      			final Window toplevel = SwingUtilities.getWindowAncestor(c);
      			toplevel.addWindowFocusListener(new WindowAdapter() 
      			{
      				@Override
      				public void windowGainedFocus(WindowEvent e) 
      				{
      					c.requestFocus();
      					toplevel.removeWindowFocusListener(this);
      				}
      			});
      			c.removeHierarchyListener(this);
      		}
      	}
      }
      
    • Thanks, work well in java 1.6.0_24 + linux ubuntu 10.04:

      
      import java.awt.Component;
      import java.awt.Window;
      import java.awt.event.HierarchyEvent;
      import java.awt.event.HierarchyListener;
      import java.awt.event.WindowAdapter;
      import java.awt.event.WindowEvent;
      import javax.swing.SwingUtilities;
      
      public class RequestFocusListener implements HierarchyListener {
      
          @Override
          public void hierarchyChanged(HierarchyEvent e) {
              final Component c = e.getComponent();
              if (c.isShowing() && (e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0) {
                  Window toplevel = SwingUtilities.getWindowAncestor(c);
                  toplevel.addWindowFocusListener(new WindowAdapter() {
      
                      @Override
                      public void windowGainedFocus(WindowEvent e) {
                          c.requestFocus();
                      }
                  });
              }
          }
      }
      
    • Anonymous said

      Worked for me too! Thanks so much!

  11. Anonymous said

    Thanks.
    This worked for me. Finally.
    I can’ believe something that simple is so hard to do in java

  12. Anonymous said

    Thanks a lot;It work perfectly!!!I was looking for this solution for a long time.

  13. Anonymous said

    Another brilliant solution by the indispensable Mr. Camick! Thanks.

  14. Martin said

    An awkward workaround nicely presented, thanks

  15. Prongs said

    I had the same issue exactly.. Your solution fixed it! Thanks a lot!!!!

  16. Coockie said

    Thx!

  17. jl said

    This will hardly work.
    Just override JOptionPane.selectInitialValue().

  18. Anonymous said

    Simple and efficient, works fine for me.
    Thank you!!!

  19. Les! said

    Excelente!!!! muchas gracias

  20. ajd.ender said

    Thank you for this.

  21. Anonymous said

    Thank you! A very easy and welcome solution for JOptionPanes.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
Follow

Get every new post delivered to your Inbox.

Join 96 other followers

%d bloggers like this: