The Design of KDialog

KDialog's constructors map to JDialog's

package kahuna.ui;

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

public class KDialog extends JDialog {
    protected KDialog(Frame owner, boolean modal) {
        super(owner, modal);
    }

    protected KDialog(Dialog owner, boolean modal) {
        super(owner, modal);
    }

    protected KDialog(Frame owner,
                      String title, boolean modal) {
        super(owner, title, modal);
    }

    protected KDialog(Dialog owner,
                      String title, boolean modal) {
        super(owner, title, modal);
    }

Always pass in a valid owner to avoid orphan dialogs.

Orphan dialogs disappear when you click on another window but they don't go away, they just slip underneath, and you have to close other windows to find them.

ExampleDialog extends, adds widgets and model

package kahuna.ui;

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

public class ExampleDialog extends KDialog {
    private static final boolean MODAL = true;
    private static final String  TITLE = "Exemplary Dialog";

    // the object whose data we're messing with
    private SomeClass dataOwner;

    // widgets
    private JLabel     nameLabel;
    private JTextField nameText;
    private JLabel     phoneLabel;
    private JTextField phoneText;
    private JCheckBox  sexCheck;

    // constructor as template method
    public ExampleDialog(Frame owner, SomeClass dataOwner) {
        super(owner, TITLE, MODAL);

        this.dataOwner = dataOwner;

        createComponents();
        initializeComponents();
        layoutComponents();
        addWiring();
        setLocationRelativeTo(owner);
    }

KDialog's builder methods include stdTextField() . . .

    public static JTextField stdTextField(int columns) {
        final JTextField tf = new JTextField(columns) {
            public Dimension getMaximumSize() {
                Dimension max = super.getMaximumSize();
                Dimension prf = getPreferredSize();
                return new Dimension(max.width, prf.height);
            }
        };
        tf.addFocusListener(new FocusAdapter() {
            public void focusGained(FocusEvent event) {
                if (!event.isTemporary()) {
                    tf.selectAll();
                }
            }
        });
        return tf;
    }

. . . stdPasswordField() . . .

    public static JPasswordField stdPasswordField(int cols) {
        final JPasswordField tf = new JPasswordField(cols) {
            public Dimension getMaximumSize() {
                Dimension max = super.getMaximumSize();
                Dimension prf = getPreferredSize();
                return new Dimension(max.width, prf.height);
            }
        };
        tf.addFocusListener(new FocusAdapter() {
            public void focusGained(FocusEvent event) {
                if (!event.isTemporary()) {
                    tf.selectAll();
                }
            }
        });
        return tf;
    }

ExampleDialog creates and initializes its components

    private void createComponents() {
        nameLabel  = new JLabel("Name:");
        nameText   = stdTextField(15);
        phoneLabel = new JLabel("Phone:");
        phoneText  = stdTextField15);
        sexCheck   = new JCheckBox("Sex");
    }

    private void initializeComponents() {

        // add mnemonics
        nameLabel.setDisplayedMnemonic('N');
        nameLabel.setLabelFor(nameText);
        phoneLabel.setDisplayedMnemonic('P');
        phoneLabel.setLabelFor(phoneText);
        sexCheck.setMnemonic('S');

        // init data widgets
        nameText.setText(dataOwner.getName());
        sexCheck.setSelected(dataOwner.isSexed());
    }

    private void addWiring() {}

. . . stdCancelButton() . . .

    protected JButton stdCancelButton() {
        JButton button = new JButton("Cancel");
        button.setMnemonic('C');
        button.setDefaultCapable(false);
        Action cancelAction = new AbstractAction("Cancel") {
            public void actionPerformed(ActionEvent event) {
                Here's the action:
                setVisible(false);
            }
        };
        button.addActionListener(cancelAction);
        mapKeyToAction(KeyEvent.VK_ESCAPE, cancelAction);
        return button;
    }

. . . stdOKButton()

    protected JButton stdOKButton() {
        JButton button = new JButton("OK");
        button.setMnemonic('O');
        getRootPane().setDefaultButton(button);
        button.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent event) {
                Here's the action:
                if (dataIsValid()) {
                    setModelFromView();
                    setVisible(false);
                }
            }
        });
        return button;
    }
Subclasses should override dataIsValid() . . .
    protected boolean dataIsValid() {
        return true;
    }
. . . and setModelFromView()
    protected void setModelFromView() {}

ExampleDialog overrides dataIsValid() . . .

(Note the chaining of validation methods.)
    protected boolean dataIsValid() {
        return nameIsOK() && phoneIsOK();
    }

    private boolean nameIsOK() {
        if (nameText.getText().length() > 0) {
            return true;
        }

        stdErrBox("You must enter a name.");
        nameText.requestFocus();

        return false;
    }

    private boolean phoneIsOK() {
        if (phoneText.getText().length() > 0) {
            return true;
        }

        stdErrBox("You must enter a phone number.");
        phoneText.requestFocus();

        return false;
    }

. . . and setModelFromView()

(This only gets called if the user clicks the OK button.)
    protected void setModelFromView() {
        dataOwner.setName(nameText.getText());
        dataOwner.setPhone(phoneText.getText());
        dataOwner.setSexed(sexCheck.isSelected());
    }

KDialog supplies standard message boxes . . .

. . . a standard "information" type box

    protected void stdInfoBox(String message) {
        stdInfoBox(this, message);
    }

. . . a standard "warning" type box

    protected void stdWarningBox(String message) {
        stdWarningBox(this, message);
    }

. . . and a standard "error" type box

    protected void stdErrBox(String message) {
        stdErrBox(this, message);
    }

KDialog also offers static versions of these methods:
    public static void stdErrBox(Component pa, String msg) {
        stdMsgBox(pa, msg, JOptionPane.ERROR_MESSAGE);
    }

The standard message boxes.

        stdInfoBox("Our new version doesn't crash as much.");

        stdWarningBox("That tie doesn't go with that shirt.");

        stdErrBox("Sorry. All your data has been lost.");

KDialog's convenience methods: stdLayout() . . .

 protected void stdLayout(Component widgetBox, 
                          JButton[] buttons) {

     // create button box
     Box buttonBox = Box.createHorizontalBox();
     buttonBox.add(Box.createHorizontalGlue());
     for (int i = 0; i < buttons.length; i++) {
         if (i > 0) {
             buttonBox.add(Box.createHorizontalStrut(5));
         }
         buttonBox.add(buttons[i]);
     }
     matchComponentSize(buttons);

     // create main box
     Box mainBox = Box.createVerticalBox();
     mainBox.add(widgetBox);
     mainBox.add(createVerticalStrut(17));
     mainBox.add(buttonBox);

     // assemble std dialog
     Container cont = getContentPane();
     cont.add(createVerticalStrut(13), BorderLayout.NORTH);
     cont.add(createVerticalStrut(10), BorderLayout.SOUTH);
     cont.add(createHorizontalStrut(11), BorderLayout.WEST);
     cont.add(createHorizontalStrut(10), BorderLayout.EAST);
     cont.add(mainBox);
     pack();
 }

ExampleDialog only lays out its own widgets

 // all routines from here on concern only visual layout
 private void layoutComponents() {
     Container widgetBox = createMainBox();
     JButton[] buttons   = { stdOKButton(), 
                             stdCancelButton() };

     stdLayout(widgetBox, buttons);
 }

. . . matchComponentSize(JComponent[]) . . .

 public static void matchComponentSize(JComponent[] comps) {
     Dimension size = new Dimension(0, 0);
     for (int i = 0; i < comps.length; i++) {
         if (comps[i].isVisible()) {
             Dimension bSize = comps[i].getPreferredSize();
             size.width = (size.width < bSize.width) ? 
                           bSize.width : size.width;
             size.height = (size.height < bSize.height) ? 
                           bSize.height : size.height;
         }
     }
     for (int i = 0; i < comps.length; i++) {
         comps[i].setPreferredSize(size);
         comps[i].setMinimumSize(size);
         comps[i].setMaximumSize(size);
     }
 }

. . . shrinkWrap(AbstractButton[]) . . .

 public static void shrinkWrap(AbstractButton[] buttons) {
     Insets shrinkWrap = new Insets(0, 0, 0, 0);
     for (int i = 0; i < buttons.length; i++) {
         buttons[i].setMargin(shrinkWrap);
     }
 }

. . . createHorizontalStrut(int width) . . .

 public static Component createHorizontalStrut(int width) {
     return Box.createRigidArea(new Dimension(width, 0));
 }

. . . and createVerticalStrut(int height)

 public static Component createVerticalStrut(int height) {
     return Box.createRigidArea(new Dimension(0, height));
 }

Boxes within boxes within boxes . . .

 private Box createMainBox() {
     Box box = Box.createVerticalBox();

     matchComponentSize(new JComponent[]{ nameLabel,
                                          phoneLabel });

     box.add(createNameTextBox());
     box.add(createVerticalStrut(5));
     box.add(createPhoneTextBox());
     box.add(createVerticalStrut(5));
     box.add(createIsSexedBox());

     return box;
 }

 private Box createNameTextBox() {
     Box box = Box.createHorizontalBox();
     
     box.add(nameLabel);
     box.add(Box.createHorizontalGlue());
     box.add(createHorizontalStrut(11));
     box.add(nameText);
     
     return box;
 }

 private Box createPhoneTextBox() {
     Box box = Box.createHorizontalBox();
     
     box.add(phoneLabel);
     box.add(Box.createHorizontalGlue());
     box.add(createHorizontalStrut(11));
     box.add(phoneText);
     
     return box;
 }

 private Box createIsSexedBox() {
     Box box = Box.createHorizontalBox();
     
     shrinkWrap(new AbstractButton[]{ sexCheck });

     box.add(sexCheck);
     box.add(Box.createHorizontalGlue());
     
     return box;
 }