|
The Java Specialists' Newsletter
Issue 012 2001-03-07
Category:
GUI
Java version: JDK 1.3 Setting focus to second component of modal dialogby Dr. Heinz M. Kabutz
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.
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.
Until next week, when I will look at what happens when you send
GUI components over the network, ideas sponsored by Niko Brummer.
Heinz
GUI Articles
Related Java Course
Discuss at The Java Specialist Club
|