Java 版 (精华区)

发信人: DreamWeaver (dw.hit.edu.cn改版啦), 信区: Java
标  题: Setting focus to second component of modal dialog
发信站: 哈工大紫丁香 (2003年05月22日13:19:17 星期四), 站内信件

http://www.smotricz.com/kabutz/Issue012.html

2001-03-07 TJSN [Issue 012] - Setting focus to second component of modal
 dialog
Author: Dr. Heinz M. Kabutz
If you are not already subscribed to this newsletter, please send an 
email to subscribe@javaspecialists.co.za. Be warned that if you are a 
beginner in Java, you will at times struggle to keep up. The archive 
of past newsletters is kept at http://www.javaspecialists.co.za 
--------------------------------------------------------------------------------

Welcome to the 12th issue of "The Java(tm) Specialists' Newsletter". 
Please forward this free newsletter to as many people as you know who 
might be interested in "advanced" Java topics. You are welcome to send 
me questions on topics in my newsletters, I will do my best to answer 
them. 

The code in these newsletters has been tested using JDK 1.3. 

Setting focus to second component of modal dialog
A few weeks ago I got stumped by a seemingly simple problem. I was 
trying to write a login dialog that would remember the last username 
entered and put the focus on the password field if an old username was 
found. I battled against the tide of Swing, even posted a question to 
the local Java User Group mailing list, but eventually I performed 
some obscure tricks to conquer this basic beginner's problem. 

---
Warning Advanced:
A problem with dialogs is that they are very often not bound to a parent
 frame, especially modal dialogs. This is not very good, because if 
you move to another application and move back to your Java application 
via the task bar in Windows, you cannot see the dialog. This single 
"bug" has caused a lot of confusion for users who think their Java 
application has hung up, but if they ALT+TAB to the application they can
 see the dialog again. A good solution is to create a frame at 
position -1000, -1000 and use that as the owner if the dialog does not 
have an owner. It is also possible to write a class which works out when
 a new window is shown and maps the title to the frame. This way you can
 find existing frames given a title. No, I won't tell you in this 
newsletter how to do that, no space. 
---


My LoginDialog looked something like this: 

  //: LoginDialog.java
  import javax.swing.*;
  import java.awt.*;
  public class LoginDialog extends JDialog {
    private final JTextField userName = new JTextField(8);
    private final JPasswordField password = new JPasswordField(8);
    public LoginDialog(Frame owner) {
      super(owner, "Login Dialog", true);
      getContentPane().setLayout(new GridLayout(0,2,5,5));
      getContentPane().add(new JLabel("Username:"));
      getContentPane().add(userName);
      getContentPane().add(new JLabel("Password:"));
      getContentPane().add(password);
      pack();
      Windows.centerOnScreen(this);
      show();
    }
    public String getUserName() { return userName.getText(); }
    public String getPassword() { return password.getText(); }
    public static void main(String[] args) {
      JFrame owner = new JFrame("Login Dialog");
      owner.setLocation(-1000, -1000);
      owner.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      owner.show();
      new LoginDialog(owner);
    }
  }
  //: Windows.java
  import java.awt.*;
  public class Windows {
    public static void centerOnScreen(Window window) {
      Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
      window.setLocation(
        (d.width - window.getSize().width) / 2,
        (d.height - window.getSize().height) / 2);
    }
  }

As mentioned before, I wanted my focus to start on the password field, 
rather than the user name field. So, the obvious place to set the 
focus is after the call to "centerOnScreen", i.e. change the code to 

  // ...
    pack();
    centerOnScreen(this);
    password.requestFocus();
    show();
  }
  // ...

Unfortunately, you can only change the focus to components which are 
visible on the screen, and since the dialog has not been shown yet, 
trying to set the focus has no effect. 

The obvious solution to this problem is to request the focus after the 
show() has been called. But, since this is a modal dialog, show will 
only return once the dialog has been closed, so even though the 
component is now visible, we will only request focus once we have closed
 the dialog, which does not help us awefully much. 

Again, the seemingly obvious solution to this problem is to call the 
requestFocus method using SwingUtilities.invokeLater(), but you are 
not guaranteed that the dialog will then be visible, and if it is not, 
you again have no effect. You could of course wait for 10 seconds and 
then request focus, but that would result in a rather awkward user 
interface. 

I posted this problem to a local Java user group and got one response to
 how this could be solved. But first I will show you my solution, 
which is terribly obscure, but I could not come up with anything better.
 Please send me your solutions if they differ from these: 

Solutions 1
We want to pass the focus on as soon as we get the focus in the username
 field. We thus add a focus listener to the userName field, which 
transfers the focus to the next component when the focusGained method is
 called. We only want to do that when the dialog is constructed, so when
 the focusLost method is called we remove the listener again. 
LoginDialog would now look like this: 

  //: LoginDialog2.java
  import javax.swing.*;
  import java.awt.*;
  import java.awt.event.*;
  public class LoginDialog2 extends JDialog {
    private final JTextField userName = new JTextField(8);
    private final JPasswordField password = new JPasswordField(8);
    public LoginDialog2(Frame owner) {
      super(owner, "Login Dialog", true);
      getContentPane().setLayout(new GridLayout(0,2,5,5));
      getContentPane().add(new JLabel("Username:"));
      getContentPane().add(userName);
      getContentPane().add(new JLabel("Password:"));
      getContentPane().add(password);
      pack();
      Windows.centerOnScreen(this);
      userName.addFocusListener(new FocusListener() {
        public void focusGained(FocusEvent e) {
          userName.transferFocus();
        }
        public void focusLost(FocusEvent e) {
          userName.removeFocusListener(this); // refers to listener
        }
      });
      show();
    }
    public String getUserName() { return userName.getText(); }
    public String getPassword() { return password.getText(); }
    public static void main(String[] args) {
      JFrame owner = new JFrame("Login Dialog");
      owner.setLocation(-1000, -1000);
      owner.show();
      owner.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      new LoginDialog2(owner);
    }
  }

Yes, it is fairly obscure, but so is solution # 2, given to me by my 
"Bruce Eckel Handson" student, Charl Smit from CCH in South Africa. 
Thanks Charl. 

Solution 2
What we can also do is issue a focus gained event for the password field
 which will be actualised once the event queue gets a chance. 

  //: LoginDialog3.java
  import javax.swing.*;
  import java.awt.*;
  import java.awt.event.*;
  public class LoginDialog3 extends JDialog {
    private final JTextField userName = new JTextField(8);
    private final JPasswordField password = new JPasswordField(8);
    public LoginDialog3(Frame owner) {
      super(owner, "Login Dialog", true);
      getContentPane().setLayout(new GridLayout(0,2,5,5));
      getContentPane().add(new JLabel("Username:"));
      getContentPane().add(userName);
      getContentPane().add(new JLabel("Password:"));
      getContentPane().add(password);
      pack();
      Windows.centerOnScreen(this);
      changeFocus(userName, password);
      show();
    }
    private void changeFocus(final Component source,
        final Component target) {
      SwingUtilities.invokeLater(new Runnable() {
        public void run() {
          target.dispatchEvent(
            new FocusEvent(source, FocusEvent.FOCUS_GAINED));
        }
      });
    }
    public String getUserName() { return userName.getText(); }
    public String getPassword() { return password.getText(); }
    public static void main(String[] args) {
      JFrame owner = new JFrame("Login Dialog");
      owner.setLocation(-1000, -1000);
      owner.show();
      owner.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      new LoginDialog3(owner);
    }
  }

This also works perfectly, but I cannot decide which is more obscure. 
I suppose the 2nd solution is "better" because we can move the focus 
changing code out of the class into a general GUI utilities class and do
 this type of focus changing in a consistent way throughout the project.
 Also, it is probably easier with the 2nd solution to hop to any 
component on the screen, rather than just transfer the focus to the next
 component. 

You be the judge. Please let me know if you have a better solution to 
this problem, by sending email to heinz@javaspecialists.co.za. 

Until next week, when I will look at what happens when you send GUI 
components over the network, ideas sponsored by Niko Brummer. 

Heinz 


------------------------------------------------------------------------
--------

(C)opyright Maximum Solutions, South Africa
Reprint Rights. Copyright subsists in all the material included in 
this email, but you may freely share the entire email with anyone you 
feel may be interested, and you may reprint excerpts both online and 
offline provided that you acknowledge the source as follows: This 
material from The Java(tm) Specialists' Newsletter by Maximum 
Solutions (South Africa). Please contact Maximum Solutions for more 
information.

Java is a trademark or registered trademark of Sun Microsystems, Inc. in
 the United States and other countries. Maximum Solutions is independent
 of Sun Microsystems, Inc. 

--

        这就是巴巴爸爸,巴巴妈妈,巴巴租,巴巴拉拉,巴巴立包,巴巴包,
    巴巴贝尔,巴巴布莱特,巴巴布拉包,听明白了吗,再说一遍 … …

※ 来源:·哈工大紫丁香 bbs.hit.edu.cn·[FROM: 202.118.239.4]
[百宝箱] [返回首页] [上级目录] [根目录] [返回顶部] [刷新] [返回]
Powered by KBS BBS 2.0 (http://dev.kcn.cn)
页面执行时间:208.789毫秒