Monday, June 8, 2009

Java: JOptionPane Examples Part 3 - Advanced

Using Event Listeners and JPanels with JOptionPane Dialogs

In my JOptionPane Examples Part 1 and Part 2, I covered many ways to use the JOptionPane, some ordinary, some surprising. Now you may be thinking that the JOptionPane is great, but that you need a level of interactivity that is cannot support. Well, think again.

I was recently asked about Example 3 (from Part 2), which shows the use of JRadioButtons (tied together with a ButtonGroup) and a JTextField. I was asked if the JTextField could have the keyboard focus by default, so that the user could immediately start typing in the JTextField, accepting the default JRadioButton.

Since the JOptionPane tends to take control of the dialog once it starts, a more subtle approach is required. One way to achieve this is to use an event listener. By adding the appropriate event listener to the JTextField object, an action can be performed after the dialog has initialized.


Example 1: Adding Event Listeners to a JTextField

This is an enhancement to the code example in Part 2, Example 3. In order to force keyboard focus into the JTextField, the use of event listeners is required. Also the array argument to showConfirmDialog() is replaced with a JPanel that will be populated with all needed components.

This example requires the following imports in addition to the imports from Part 2:

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

As in the previous version, a couple of labels, some JRadioButtons, a ButtonGroup, and a JTextField are defined.

    JLabel option_label = new JLabel("Select an option:");

JRadioButton b1 = new JRadioButton("Option 1");
JRadioButton b2 = new JRadioButton("Option 2");
JRadioButton b3 = new JRadioButton("Option 3");
b1.setSelected(true);

ButtonGroup group = new ButtonGroup();
group.add(b1);
group.add(b2);
group.add(b3);

JLabel name_label = new JLabel("Enter a name:");

JTextField name = new JTextField(30);
 

In this version, two listeners are added, a ComponentListener and an AncestorListener. Both of these attempt to perform a requestFocus(), when the event is triggered.

Why are both needed? The ComponentListener approach seems to work great until Java 6.0. Unfortunately, Java 6.0 seems to have broken it, which is why the AncestorListener is used as well. Also unfortunately, the AncestorListener approach doesn't work in JVMs prior to 6.0. Fortunately, both approaches can exist together, if you need a solution for both sets of JVMs.

Adding the ComponentListener to the JTextField component (for JVMs prior to Java 6.0):

    name.addComponentListener(new ComponentListener() {
public void componentHidden(ComponentEvent ce) {}
public void componentMoved(ComponentEvent ce) {
ce.getComponent().requestFocus();
}
public void componentResized(ComponentEvent ce) {
ce.getComponent().requestFocus();
}
public void componentShown(ComponentEvent ce) {}
});
 

Adding the AncestorListener to the JTextField component (for Java 6.0 JVM):

    name.addAncestorListener(new AncestorListener(){   
public void ancestorAdded(AncestorEvent ae){
ae.getComponent().requestFocus();
}
public void ancestorRemoved(AncestorEvent ae){}
public void ancestorMoved(AncestorEvent ae){}
});
 

Now the JPanel is defined and populated. A BoxLayout is used to position the components vertically. The components are placed in the order that they are added to the panel, which is done by using the add() method.

    JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel,BoxLayout.Y_AXIS));
panel.add(option_label);
panel.add(b1);
panel.add(b2);
panel.add(b3);
panel.add(name_label);
panel.add(name);
 

The only other change to the previous version is that the second argument to showConfirmDialog() is changed from an array to the JPanel, which is called panel.

    int res = JOptionPane.showConfirmDialog(null, panel, "Select", 
JOptionPane.OK_CANCEL_OPTION);
 

The existing result processing code can be used unchanged and therefore will not be duplicated here.


Example 2: Adding an Event Listener to a JPasswordField

Another example that could use an event listener is the JPasswordField. In Example 1 (Part 2), I discussed creating a login/password dialog box using JOptionPane. In this example, the previous code will be enhanced to demonstrate dynamic use of the setEchoChar() method.

Lotus Notes has had a curious password entry feature for a long time. When entering a password, the password masking character changes with each key press. The value of this is debatable, but reproducing this feature presents an interesting challenge. This phenomenon can be achieved in Java by using a KeyListener.

This example requires the following imports in addition to the imports from Part 2:
    import java.awt.event.*;
 

The code for defining the dialog components, is identical.

    JLabel label_loginname = new JLabel("Enter your login name:");
JTextField loginname = new JTextField(15);

JLabel label_password = new JLabel("Enter your password:");
JPasswordField password = new JPasswordField();

JCheckBox rememberCB = new JCheckBox("Remember me");
 

Now a KeyListener is added to the password field. First a list of characters to use for the password masking is defined as a char array. Then each of the KeyListener methods must be defined, even if they are not used.

The keyTyped() method is using for the activity. First the source component is extracted using the getSource() method. Then the setEchoChar() method of the JPasswordField is called. A random char from the char array is passed to setEchoChar() as its only argument.

    password.addKeyListener(new KeyListener() {
char[] mask = { '@', '#', '$', '%', '&', '*' };
public void keyPressed(java.awt.event.KeyEvent ke) {}
public void keyReleased(java.awt.event.KeyEvent ke) {}
public void keyTyped(java.awt.event.KeyEvent ke) {
JPasswordField source = (JPasswordField)ke.getSource();
source.setEchoChar(mask[(int)(mask.length*Math.random())]);
}
});
 

As each character is typed into the password field, that event is fired, which is caught by the listener. The listener then executes and changes the masking character using setEchoChar().

The array definition and call to showConfirmDialog() are repeated here for reference, but are unchanged from the original example.

    Object[] array = { label_loginname, 
loginname,
label_password,
password,
rememberCB };


int res = JOptionPane.showConfirmDialog(null, array, "Login",
JOptionPane.OK_CANCEL_OPTION,
JOptionPane.PLAIN_MESSAGE);
 


As you can see there are many ways to add interactivity to JOptionPane dialogs.

No comments: