Speed up your Swing GUI construction with better building blocks

Use two helper classes to reduce dialog development time

This article introduces two helper classes for speeding up the construction of Swing user interfaces. The idea is that these classes encapsulate the layout of form and data entry dialogs in a standard way. Specific dialogs can be quickly constructed by simply adding the necessary dialog fields to the two classes, without worrying about the layout of the fields.

The two classes are:

  • LabelledItemPanel: This class lays out items and their labels neatly on a panel
  • StandardDialog: This class provides the general look of dialogs and default button processing

These two classes leverage the fact that most dialogs used for entering information usually have the same basic layouts. Look at three dialogs for entering customer, product, and employee data:

Figure 1. Customer details dialog
Figure 2. Product details dialog
Figure 3. Employee details dialog

Each dialog has the same layout consisting of a list of labelled fields. Each field’s layout is the same. The dialogs all have the normal Ok and Cancel buttons present on most data entry dialogs.

On our project, we achieved significant time savings as a result of using these classes instead of an IDE graphical user interface (GUI) builder. We had to build more than 20 dialogs for performing create, modify, and view operations on various data types. At the start of the project, a first version of the LabelledItemPanel and StandardDialog were built. Both classes had a few alignment problems. However, all the required panels and dialogs were created without programmers worrying about layout aspects. Further into the project, the alignment problems were solved in LabelledItemPanel and StandardDialog. All the panels and dialogs that used these two classes were rectified. This approach allowed developers to integrate the GUI with the application code much faster than if they were required to fully complete individual dialogs.

In this article, we compare our approach to the popular approach of using an IDE GUI builder. We explain how we came up with the idea for the classes and how they save development time. We describe our two classes and their usage in detail.

Note: Download this article’s source code from Resources.

Using an IDE GUI builder

Most IDEs today have some sort of capability to construct user interfaces without writing much code. This is an attractive option to many developers because writing user interfaces from scratch can be complex. However, using an IDE GUI builder to build numerous windows or dialogs has its disadvantages. The next section explains these disadvantages.

The myth versus reality for large user interfaces

An IDE is a good tool when you need to build a simple GUI with only a small number of windows or dialogs. If you need to build a large number of windows or dialogs, then using an IDE GUI builder has the following disadvantages:

  • Unless you use simple layout managers or absolute positioning you still need to understand the complexities of various layout managers, especially GridBagLayout.

  • Each item on the dialog needs to be individually laid out, which involves setting a number of properties for each item. This makes it slow and tedious to lay out large numbers of dialogs or dialogs with many fields.

  • You cannot use the same layout over similar screens. Each new dialog needs to be laid out from scratch.

  • The generated code is generally not portable between IDEs and is sometimes not even compatible between different IDE versions. This is an important fact that must be considered in regards to code maintenance.

  • Dialogs are not easy to modify because the layout constraints are assigned to each item. If an item is moved, its constraints and all the other items’ constraints affected by the moved item will need adjusting as well.

  • If there is a bug, looking through the generated code can be frightening, as generated code (in most cases) is not well laid out or readable. For example, the following is code generated by a popular IDE’s GUI builder for a customer data entry panel:

public class JBuilderPanel extends JPanel { ... GridBagLayout gridBagLayout1 = new GridBagLayout(); private JLabel myCustomerCodeLabel = new JLabel(); private JTextField myCustomerCodeField = new JTextField(); private JLabel myNameLabel = new JLabel(); private JTextField myNameField = new JTextField(); private JLabel myAddressLabel = new JLabel(); private JScrollPane myAddressScrollpane = new JScrollPane(); private JTextArea myAddressField = new JTextArea(); ...

public JBuilderPanel() { try { jbInit(); } catch(Exception e) { e.printStackTrace(); } }

private void jbInit() throws Exception { this.setLayout(gridBagLayout1); this.setBorder(BorderFactory.createEtchedBorder()); myCustomerCodeLabel.setText("Customer Code"); myNameLabel.setText("Name"); myAddressLabel.setText("Address"); ...

this.add(myCustomerCodeLabel, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.NORTHEAST, GridBagConstraints.NONE, new Insets(10, 10, 0, 0), 0, 0));

this.add(myCustomerCodeField, new GridBagConstraints(1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(10, 10, 0, 10), 0, 0));

this.add(myNameLabel, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.NORTHEAST, GridBagConstraints.NONE, new Insets(10, 0, 0, 0), 0, 0));

this.add(myNameField, new GridBagConstraints(1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(10, 10, 0, 10), 0, 0));

this.add(myAddressLabel, new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.NORTHEAST, GridBagConstraints.NONE, new Insets(10, 10, 0, 0), 0, 0)); this.add(myAddressScrollpane, new GridBagConstraints(1, 2, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(10, 10, 0, 10), 0, 0)); ... }

... }

An alternative approach

At the start of our project we looked for alternatives to using an IDE to build the dialogs because we had many dialogs to build and were aware of the disadvantages described above. The only alternative was to hand code the dialogs and their associated panels. However, we were not going to be any better off simply using Swing’s layout managers and components. We would end up producing similar code to the IDE with the same drawbacks. To speed up development we needed better building blocks (classes) that would take care of the dialogs’ common aspects and be easily customized to handle the differences between screens.

What is common? What is different?

We determined the following three elements were common to most of our dialogs:

  • The dialogs usually consisted of a list of items and labels neatly aligned down the page
  • Each dialog normally had Ok and Cancel buttons
  • The actions performed when the Ok and Cancel buttons were pressed were usually the same

The differences between screens were the actual fields being edited and their labels.

Better building blocks

The most tedious part of developing the dialogs was specifying the layout constraints for each field and its label. The fastest way to develop is for the classes to allow us to specify the fields and their labels without specifying the layout constraints. It seemed to make sense to use two different classes: one for handling layout of the labelled fields and one for the common dialog buttons and associated processing. We called the class for layout handling LabelledItemPanel, while we called the class for the common dialog features StandardDialog.

A common panel: LabelledItemPanel

The purpose of LabelledItemPanel is to lay out items and their labels neatly on a panel. The class uses a GridBagLayout to control the actual layout, but this is all hidden from class users. To use the class, the item and its label are added to the panel with the addItem() method, passing the text for the field label and the field component. The user does not have to specify any layout constraints because the class works out all of the layout constraints needed.

The LabelledItemPanel can be used in two ways:

  • Create an instance of the LabelledItemPanel and add data entry fields to the panel
  • Subclass LabelledItemPanel and add the data entry fields to the panel during construction

An example of using LabelledItemPanel directly is shown in the following code fragment:

JTextField customerCodeField = new JTextField(); JTextField nameField = new JTextField(); JTextArea addressField = new JTextArea(3, 20); ...

LabelledItemPanel panel = new LabelledItemPanel()

panel.setBorder(BorderFactory.createEtchedBorder());

panel.addItem("Customer Code", customerCodeField); panel.addItem("Name", nameField); panel.addItem("Address", new JScrollPane(addressField, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER)); ...

// Display the panel to the User in a dialog

String customerCode = customerCodeField.getText(); String name = nameField.getText(); String address = addressField.getText(); ...

// Process the data

If you compare the simple addItem() method calls with the IDE-generated code shown below, the time-saving advantages of using this class are quite obvious:

  this.add(myCustomerCodeLabel,
    new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0,
      GridBagConstraints.NORTHEAST, GridBagConstraints.NONE,
      new Insets(10, 10, 0, 0), 0, 0));
  this.add(myCustomerCodeField,
     new GridBagConstraints(1, 0, 1, 1, 1.0, 0.0,
       GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL,
       new Insets(10, 10, 0, 10), 0, 0));
  this.add(myNameLabel, 
    new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0,
      GridBagConstraints.NORTHEAST, GridBagConstraints.NONE,
      new Insets(10, 0, 0, 0), 0, 0));

An example of using the LabelledItemPanel by subclassing is shown in the following code fragment:

public class CustomerPanel extends LabelledItemPanel { private JTextField myCustomerCodeField = new JTextField(); private JTextField myNameField = new JTextField(); private JTextArea myAddressField = new JTextArea(3, 20); ...

public CustomerPanel() { init(); }

private void init() { setBorder(BorderFactory.createEtchedBorder());

addItem("Customer Code", myCustomerCodeField); addItem("Name", myNameField); addItem("Address", new JScrollPane(myAddressField, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER)); ... }

public CustomerData getCustomerData() { CustomerData customerData = new CustomerData();

customerData.myCustomerCode = myCustomerCodeField.getText(); customerData.myName = myNameField.getText(); customerData.myAddress = myAddressField.getText(); ...

return customerData; }

... }

// A method in some class using the CustomerPanel { CustomerPanel panel = new CustomerPanel();

// Display the panel to the User in a dialog

CustomerData customerData = panel.getCustomerData();

// Process the data }

LabelledItemPanel under the hood

This section explains how LabelledItemPanel works. The code for the class is shown below:

public class LabelledItemPanel extends JPanel { /** The row to add the next labelled item to */ private int myNextItemRow = 0;

public LabelledItemPanel() { init(); }

private void init() { setLayout(new GridBagLayout());

// Create a blank label to use as a vertical fill so that the // label/item pairs are aligned to the top of the panel and are not // grouped in the centre if the parent component is taller than // the preferred size of the panel.

GridBagConstraints constraints = new GridBagConstraints(); constraints.gridx = 0; constraints.gridy = 99; constraints.insets = new Insets(10, 0, 0, 0); constraints.weighty = 1.0; constraints.fill = GridBagConstraints.VERTICAL;

JLabel verticalFillLabel = new JLabel();

add(verticalFillLabel, constraints); }

public void addItem(String labelText, JComponent item) { // Create the label and its constraints

JLabel label = new JLabel(labelText);

GridBagConstraints labelConstraints = new GridBagConstraints();

labelConstraints.gridx = 0; labelConstraints.gridy = myNextItemRow; labelConstraints.insets = new Insets(10, 10, 0, 0); labelConstraints.anchor = GridBagConstraints.NORTHEAST; labelConstraints.fill = GridBagConstraints.NONE;

add(label, labelConstraints);

// Add the component with its constraints

GridBagConstraints itemConstraints = new GridBagConstraints();

itemConstraints.gridx = 1; itemConstraints.gridy = myNextItemRow; itemConstraints.insets = new Insets(10, 10, 0, 10); itemConstraints.weightx = 1.0; itemConstraints.anchor = GridBagConstraints.WEST; itemConstraints.fill = GridBagConstraints.HORIZONTAL;

add(item, itemConstraints);

myNextItemRow++; } }

The class extends Swing’s JPanel and has two additional methods init() and addItem(). The init() is called from the constructor and sets up GridBagLayout used by the panel. We have already shown the second method, addItem(), being used. This method creates a JLabel for the label text and then adds JLabel and passed-in item to the panel with the correct GridBagConstraints. The constraints are set up so that the label text and the item are properly aligned, and that the item extends to the dialog’s right-hand edge.

A common dialog

The purpose of the class StandardDialog is to hold a data entry panel (such as a LabelledItemPanel), provide the standard Ok and Cancel buttons, and handle basic processing when the Ok or Cancel buttons are pressed.

The StandardDialog is modal, so a call to the show() method is a blocking call. After calling the show() method, the hasUserCancelled() method should be called to see if the user has cancelled the dialog by either pressing the Cancel button or the dialog close button (on the dialog frame).

StandardDialog can be used in two ways:

  • Create an instance of StandardDialog and add a data entry panel to the dialog
  • Subclass StandardDialog and add a data entry panel to the dialog during construction

An example of using StandardDialog directly is shown in the following code fragment:

// Create a data entry panel

StandardDialog dialog = new StandardDialog( (Frame)null, "Customer Details");

dialog.setContentPane(panel);

dialog.pack();

dialog.show();

if(!dialog.hasUserCancelled()) { // Process the data in the panel }

An example of using StandardDialog by subclassing is shown in the following code fragment:

public class CustomerDialog extends StandardDialog { private JTextField myCustomerCodeField = new JTextField(); private JTextField myNameField = new JTextField(); private JTextArea myAddressField = new JTextArea(3, 20); ...

private LabelledItemPanel myContentPane = new LabelledItemPanel();

public CustomerDialog() { init(); }

private void init() { setTitle("Customer Dialog");

myContentPane.setBorder(BorderFactory.createEtchedBorder());

myContentPane.addItem("Customer Code", myCustomerCodeField); myContentPane.addItem("Name", myNameField); myContentPane.addItem("Address", new JScrollPane(myAddressField, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER)); ...

setContentPane(myContentPane); }

public CustomerData getCustomerData() { CustomerData customerData = new CustomerData();

customerData.myCustomerCode = myCustomerCodeField.getText(); customerData.myName = myNameField.getText(); customerData.myAddress = myAddressField.getText(); ...

return customerData; } }

// A method in some class using the CustomerDialog { CustomerDialog dialog = new CustomerDialog();

dialog.pack();

dialog.show();

if(!dialog.hasUserCancelled()) { CustomerData customerData = dialog.getCustomerData();

// Process the data } }

If data validation needs to be performed before the dialog closes, then the isValidData() method should be overridden. An example of this is given below:

protected boolean isValidData() { if(myCustomerCodeField.getText().equals("")) { JOptionPane.showMessageDialog(this, "Please enter a Customer Code", "Blank Customer Code", JOptionPane.WARNING_MESSAGE);

myCustomerCodeField.requestFocus();

return false; }

if(myNameField.getText().equals("")) { JOptionPane.showMessageDialog(this, "Please enter a Name", "Blank Name", JOptionPane.WARNING_MESSAGE);

myNameField.requestFocus();

return false; }

return true; }

StandardDialog behind the scenes

This section explains how StandardDialog works. The appearance and behavior of StandardDialog is set up in the init() method shown below:

private void init() { setModal(true); setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);

// Setup the internal content pane to hold the user content pane // and the standard button panel

JPanel internalContentPane = new JPanel();

internalContentPane.setLayout( new BorderLayout(COMPONENT_SPACING, COMPONENT_SPACING));

internalContentPane.setBorder( BorderFactory.createEmptyBorder(COMPONENT_SPACING, COMPONENT_SPACING, COMPONENT_SPACING, COMPONENT_SPACING));

// Create the standard button panel with "Ok" and "Cancel"

Action okAction = new AbstractAction("Ok") { public void actionPerformed(ActionEvent actionEvent) { if(isValidData()) { myIsDialogCancelled = false;

dispose(); } } };

Action cancelAction = new AbstractAction("Cancel") { public void actionPerformed(ActionEvent actionEvent) { myIsDialogCancelled = true;

dispose(); } };

JPanel buttonPanel = new JPanel();

buttonPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));

buttonPanel.add(new JButton(okAction)); buttonPanel.add(new JButton(cancelAction));

internalContentPane.add(buttonPanel, BorderLayout.SOUTH);

// Initialise the user content pane with a JPanel

setContentPane(new JPanel(new BorderLayout()));

super.setContentPane(internalContentPane);

// Finally, add a listener for the window close button. // Process this event the same as the "Cancel" button.

WindowAdapter windowAdapter = new WindowAdapter() { public void windowClosing(WindowEvent windowEvent) { myIsDialogCancelled = true;

dispose(); } };

addWindowListener(windowAdapter); }

The init() is called from the constructor. The method does the following:

  • Sets up actions for the Ok and Cancel buttons. These actions set the field myIsDialogCancelled to indicate which button has been pressed and then call dispose, which causes the show() method to unblock. The method hasUserCancelled() checks the value of myIsDialogCancelled as shown in the usage example.

  • Builds the two buttons and ties them to the actions. The two buttons are added to a panel, and the panel is then added to the dialog.

  • Sets up an event handler for the close window event. This event handler performs the same processing as the action for the Cancel button.

Note: Download the full source code for LabelledItemPanel and StandardDialog from Resources.

Advantages

We have saved substantial development time with this approach. The average time spent building a panel using an IDE GUI builder was between one and three hours depending on the number of fields on the panel. Using our LabelledItemPanel class, the panels were constructed in less than 20 minutes. We have used LabelledItemPanel 35 times.

The other advantages of this approach include:

  • Absolutely no layout constraints need to be understood or specified. This allows developers with little Swing knowledge to quickly construct user interfaces.

  • Consistency of data entry panels and dialogs, both for users and developers.

  • Reordering the fields is simple. You only need to reorder. addItem() calls. You do not have to adjust constraints on all of the reordered fields and labels.

  • The layout and properties for all entry panels can be changed centrally.

  • No unnecessary references are held for labels.

Further enhancements

Our simple classes could be further enhanced to include the following:

  • LabelledItemPanel could support a read-only flag for viewing, but not editing, data
  • LabelledItemPanel could support I18N by passing in a ResourceBundle key for a label instead of passing in text already internationalized

The need for speed

We have described an approach that speeds up the construction of Swing user interfaces by using better building blocks than ones available in the Swing libraries. It relies on two classes: LabelledItemPanel and StandardDialog. The essence of our approach was to identify repetitive elements of GUIs that could be encapsulated in easily reusable code.

David
Fraser is a senior software engineer specializing in user
interfaces and has worked on software in the financial,
telecommunications, and interactive television industries. He has
been developing user interfaces for eight years on DOS, Windows,
and Unix platforms, using the BASIC, C, C++, VB, and Java
languages. He has been developing user interfaces with Swing for
the last three years.

Michael Harris is a software architect. He has
spent a number of years developing application frameworks for
Internet and network management applications for defense,
telecommunications, and television industries. He is always looking
for ways to make his fellow programmers’ lives easier. He has been
using Java for the past five years.

Source: www.infoworld.com