Java Swing GUI design with FREE tools

JGoodies Forms

When we build a Java Swing application, the GUI design could end up being a very time consuming process. The perfectly aligned components of a data entry form may look awful on a screen with a different resolution.  Aligning buttons with proper spacing could become a never ending struggle.

However many IDEs like Netbeans have nice WYSIWYG GUI builders to help developers to easily build Swing based screens. But everything comes with a price :-) . If we use those GUI builders we could end up having IDE generated code with comments like:

/**
* WARNING: Do NOT modify this code. The content of this method is
* always regenerated by the Form Editor.
*/ 

The WARNING sign reminds the consequences that we will have to face by using an IDE to build the Swing screens. These consequences may include but not limited to the points specified below.

  1. The generated code should not be edited and we have to live with what ever the code that IDE throws at us
  2. Even a small change to the GUI design requires to regenerate the entire GUI code for that screen. This may require to use the same IDE version to maintain the integrity of the code. Also this may require lot more testing as the IDE is directly modifying the existing code.
  3. Having IDE generated code intertwined with developer code may lead to maintenance problems in future, even though this could be minimized by separating generated code as much as possible
JGoodies forms library and Abeille forms designer are two great tools (of course, they are free :-) ) that we could use to step away from the above mentioned issues. Abeille form designer works as a GUI front end for the JGoodies forms library and it generates the GUI design as an XML file. JGoodies also has ButtonBarFactory that we can use to create a nicely aligned button panel.
Now let’s create a simple screen to capture user information using Abeille form designer. We will use Abeille version 2.1.0 M1. The user interface of this tool is very intuitive and you will easily figure out the things.
Steps to follow:
  1. Download and unzip the Abeille form designer and run the designer.jar
  2. Create a new project by selecting  “File –> New Project”
  3. Go to Tools–>Preferences and  click the Store forms as XML check box on the Projects tab.
  4. Create a new form by selecting  “File–>New Form”
  5. The resulted grid can be easily modified by using the buttons at the top. All the buttons have tool tips that are very helpful and self explanatory.
  6. Change the size of the columns and rows of the grid by dragging from the two pegs provided in each axis (figure 1)
  7. The components at the left tool box does not work like typical drag-and-drop components.  Click on any component on the tool box and then click on a cell of the grid to add the component to the grid
  8. Name the components accordingly as we are going to use this name to reference the components in the code
  9. Click on the label “First Name” and then change the column properties using the collapsing menus at the right side. (figure 2)

Here is a screen shot of the form once we add all the components.

[Figure 1]
The two circles on each axis of the grid shows the pegs that can be used to change the column and row sizes.
The circle at the bottom shows the project name and absolute path.
Note that the “countryCombo” and “Button bar” are just JLabel components. Later we will replace them with a JComboBox and a JPanel with JButtons respectively.
[Figure 2]
By setting the column size to “Component” the size automatically adjust with the component length.
We will do this to the column with the labels.
There are lot more features in this tool and it is best to read the documentation to learn more about them.

Now let’s create a Java project and use the XML file generated by Abeille tool and JGoodies Forms library to build our form. Before proceed, make sure to add all the required jar files to the project class path. Class path of my project looks like below(figure 3)
[Figure 3]
Below is the code to use our form in the application. You may notice following things in this code.
  1. Cleaner code, NO IDE generated code intertwined with the application code.
  2. We have the liberty to swap any component in the GUI panel with any desired component
  3. It is easy to reuse the GUI components even in any other class, only thing we have to do is to read the form xml from that class
  4. If we have to make a small change (add a new label, change spacing etc.) we can do it in Abeille form designer. There is nothing to change in the Java code, hence it requires no additional testing to test the existing features.
  5. ButtonBarFactory takes care of the spacing and sizing of the buttons
package com.jayanath.swingtest;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JTextField;
import javax.swing.border.Border;
import javax.swing.border.EtchedBorder;

import com.jeta.forms.components.panel.FormPanel;
import com.jgoodies.forms.factories.ButtonBarFactory;

/**
* <p>Class to show how to use the Swing GUI components designed using
* Abeille form designer. This will also demonstrate of how to use
* JGoodies button factories to generate uniform button panels.</p>
*
* @author jayanath.amaranayake
*/
public class UserInfo {
//these constants were generated by selecting "Form-->Export Names" on the Abeille form designer.
//after hit the OK button on the dialog, it copies these constants on to the clipboard.
//then we can simply paste them here.
public static final String ID_BUTTONBAR = "buttonBar";  //com.jeta.forms.components.label.JETALabel
public static final String ID_FIRSTNAME = "firstName";  //javax.swing.JTextField
public static final String ID_LASTNAME = "lastName";  //javax.swing.JTextField
public static final String ID_STREET1 = "street1";  //javax.swing.JTextField
public static final String ID_STREET2 = "street2";  //javax.swing.JTextField
public static final String ID_CITY = "city";  //javax.swing.JTextField
public static final String ID_COUNTRYCOMBO = "countryCombo";  //com.jeta.forms.components.label.JETALabel
public static final String ID_MALERADIOBTN = "maleRadioBtn";  //javax.swing.JRadioButton
public static final String ID_FEMALERADIOBTN = "femaleRadioBtn";  //javax.swing.JRadioButton
public static final String ID_ZIP = "zip";  //javax.swing.JTextField

/**
* Populate a panel using the Abeille form designer
* @return a JPanel instance
*/
public JPanel populateMainPanel(){
//the FormPanel derived from JPanel
FormPanel mainPanel = new FormPanel("userInfo.xml");

//let's create a combo box with values and add it to the panel
String [] countries = new String []{"Sri Lanka", "USA", "China"};
JComboBox countryCombo = new JComboBox(countries);

//now we can simply swap the label that we put in the place where
//we need to set the actual combo box. Likewise we can swap any component
//on the panel.
mainPanel.getFormAccessor().replaceBean(ID_COUNTRYCOMBO, countryCombo);

//let's set some default values to other components
//this shows you how to access any of the component
//the API supports many default component types
JTextField street1 = mainPanel.getTextField(ID_STREET1);
street1.setText("Patroon Drive");
JRadioButton mRadioBtn = mainPanel.getRadioButton(ID_MALERADIOBTN);
mRadioBtn.setSelected(true);

//we will use the ButtonBarFactory to generate our buttons panel
//this JGoodies API provides many useful methods.
//first, let's create our buttons
JButton okBtn = new JButton("OK");
JButton canBtn = new JButton("Cancel");
JButton regBtn = new JButton("Register me later");
//now generate the button bar panel
JPanel btnPanel = ButtonBarFactory.buildCenteredBar(okBtn, canBtn, regBtn);
//next, we can replace the buttonBar label with this new button bar panel
mainPanel.getFormAccessor().replaceBean(ID_BUTTONBAR, btnPanel);

//finally, let's add a nice boarder to our panel
Border etchBorder = BorderFactory.createEtchedBorder(EtchedBorder.LOWERED);
Border titleBorder = BorderFactory.createTitledBorder(etchBorder, "User Information Form");
mainPanel.setBorder(titleBorder);

return mainPanel;
}
/**
* @param args
*/
public static void main(String[] args) {
UserInfo uInfo = new UserInfo();
JFrame frame = new JFrame("Panel created using Abeille form designer");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(uInfo.populateMainPanel());
frame.pack();
frame.setVisible(true);
}

}

Here is the what we see when we run the application.(figure 4)

[Figure 4]

Note the buttons are evenly spaced and properly sized.

To give a comparison I created the same UI using Netbeans 7.0 GUI Builder. Figure 5 shows the screen generated using GUI builder of the IDE.
[Figure 5]
Netbeans 7.0 GUI Builder is very easy to use and I created the above screen in few minutes. However look at the code behind this screen and you will realize how hard it is to maintain such code.
I made it folded as it is very lengthy and I’m sure that you will not read it till end ;-) Click below to open the folded code.

package com.jayanath.swingtest;

import java.awt.Dimension;
import javax.swing.JFrame;

/**
* User Info panel GUI generated using the GUI builder
* @author jayanath.amaranayake
*/
public class UserInfoPanel extends javax.swing.JPanel {

/** Creates new form UserInfoPanel */
public UserInfoPanel() {
initComponents();
}

/** This method is called from within the constructor to
* initialize the form.
* WARNING: Do NOT modify this code. The content of this method is
* always regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">
private void initComponents() {

jLabel1 = new javax.swing.JLabel();
jTextField1 = new javax.swing.JTextField();
jLabel2 = new javax.swing.JLabel();
jTextField2 = new javax.swing.JTextField();
jLabel3 = new javax.swing.JLabel();
jRadioButton1 = new javax.swing.JRadioButton();
jRadioButton2 = new javax.swing.JRadioButton();
jLabel5 = new javax.swing.JLabel();
jLabel6 = new javax.swing.JLabel();
jLabel7 = new javax.swing.JLabel();
jLabel8 = new javax.swing.JLabel();
jLabel9 = new javax.swing.JLabel();
jLabel10 = new javax.swing.JLabel();
jToggleButton1 = new javax.swing.JToggleButton();
jToggleButton2 = new javax.swing.JToggleButton();
jToggleButton3 = new javax.swing.JToggleButton();
jComboBox1 = new javax.swing.JComboBox();
jTextField3 = new javax.swing.JTextField();
jTextField4 = new javax.swing.JTextField();
jTextField5 = new javax.swing.JTextField();
jTextField6 = new javax.swing.JTextField();

setBorder(javax.swing.BorderFactory.createTitledBorder("User Information Form"));

jLabel1.setText("First Name");

jTextField1.setText("First Name");
jTextField1.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
jTextField1ActionPerformed(evt);
}
});

jLabel2.setText("Last Name");

jTextField2.setText("Last Name");

jLabel3.setText("Gender");

jRadioButton1.setText("Male");

jRadioButton2.setText("Female");

jLabel6.setText("Street 1");

jLabel7.setText("Street 2");

jLabel8.setText("City");

jLabel9.setText("Zip Code");

jLabel10.setText("Country");

jToggleButton1.setText("OK");

jToggleButton2.setText("Cancel");

jToggleButton3.setText("Register me later");

jComboBox1.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "Item 1", "Item 2", "Item 3", "Item 4" }));

jTextField3.setText("Street 1");

jTextField4.setText("Street 2");

jTextField5.setText("City");

jTextField6.setText("Zip Code");

javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
.addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
.addGap(145, 145, 145)
.addComponent(jToggleButton1)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jToggleButton2)
.addGap(18, 18, 18)
.addComponent(jToggleButton3))
.addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(jLabel1)
.addComponent(jLabel2)
.addComponent(jLabel3))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
.addGroup(layout.createSequentialGroup()
.addComponent(jRadioButton1)
.addGap(18, 18, 18)
.addComponent(jRadioButton2))
.addComponent(jTextField2, javax.swing.GroupLayout.DEFAULT_SIZE, 235, Short.MAX_VALUE)
.addComponent(jTextField1))))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 48, Short.MAX_VALUE)
.addComponent(jLabel5)
.addGap(28, 28, 28))
.addGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(jLabel6)
.addComponent(jLabel8)
.addComponent(jLabel10)
.addComponent(jLabel7))
.addGap(18, 18, 18)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(jComboBox1, 0, 222, Short.MAX_VALUE)
.addComponent(jTextField3, javax.swing.GroupLayout.DEFAULT_SIZE, 222, Short.MAX_VALUE)
.addComponent(jTextField5, javax.swing.GroupLayout.DEFAULT_SIZE, 222, Short.MAX_VALUE)
.addComponent(jTextField4, javax.swing.GroupLayout.DEFAULT_SIZE, 222, Short.MAX_VALUE))
.addGap(12, 12, 12)
.addComponent(jLabel9)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(jTextField6, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(81, 81, 81))))
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addGap(25, 25, 25)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addGap(120, 120, 120)
.addComponent(jLabel5))
.addGroup(layout.createSequentialGroup()
.addGap(18, 18, 18)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(jLabel1)
.addGroup(layout.createSequentialGroup()
.addGap(38, 38, 38)
.addComponent(jLabel2)
.addGap(27, 27, 27)
.addComponent(jLabel3))
.addGroup(layout.createSequentialGroup()
.addComponent(jTextField1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(18, 18, 18)
.addComponent(jTextField2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(24, 24, 24)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(jRadioButton1)
.addComponent(jRadioButton2))))
.addGap(18, 18, 18)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(jLabel6)
.addGroup(layout.createSequentialGroup()
.addGap(38, 38, 38)
.addComponent(jLabel7))
.addGroup(layout.createSequentialGroup()
.addComponent(jTextField3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(18, 18, 18)
.addComponent(jTextField4, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
.addGap(18, 18, 18)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(jLabel8)
.addComponent(jTextField6, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(jTextField5, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(jLabel9))
.addGap(18, 18, 18)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(jLabel10)
.addComponent(jComboBox1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 19, Short.MAX_VALUE)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(jToggleButton1)
.addComponent(jToggleButton2)
.addComponent(jToggleButton3))))
.addContainerGap())
);
}// </editor-fold>

private void jTextField1ActionPerformed(java.awt.event.ActionEvent evt) {
// TODO add your handling code here:
}

// Variables declaration - do not modify
private javax.swing.JComboBox jComboBox1;
private javax.swing.JLabel jLabel1;
private javax.swing.JLabel jLabel10;
private javax.swing.JLabel jLabel2;
private javax.swing.JLabel jLabel3;
private javax.swing.JLabel jLabel5;
private javax.swing.JLabel jLabel6;
private javax.swing.JLabel jLabel7;
private javax.swing.JLabel jLabel8;
private javax.swing.JLabel jLabel9;
private javax.swing.JRadioButton jRadioButton1;
private javax.swing.JRadioButton jRadioButton2;
private javax.swing.JTextField jTextField1;
private javax.swing.JTextField jTextField2;
private javax.swing.JTextField jTextField3;
private javax.swing.JTextField jTextField4;
private javax.swing.JTextField jTextField5;
private javax.swing.JTextField jTextField6;
private javax.swing.JToggleButton jToggleButton1;
private javax.swing.JToggleButton jToggleButton2;
private javax.swing.JToggleButton jToggleButton3;
// End of variables declaration

/**
* This is the only method I wrote, everything else is generated by the
* IDE
* @param arg
*/
public static void main(String arg[]) {
UserInfoPanel panel = new UserInfoPanel();
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(panel);
frame.pack();
frame.setVisible(true);
}
}

Conclusion

The IDEs  are very capable of generating Swing based screens and they are great in prototyping and building small Swing applications.  Unlike the IDEs the Abeille Form Designer externalizes the GUI design code in to an XML file hence allow to maintain cleaner and more maintainable code.

There are other powerful Swing GUI building tools available but they are not FREE. If possible I would use something like JFormDesigner but the license fee is 160 USD per user. Hence I would rather use Abeille Form Designer when possible.

References

  1. JGoodies libraries
  2. Abeille Forms Designer – this is currently not available for downloading. They are probably making some changes. Below is an excerpt from the Abeille license document.  As per the license I may be able to share a copy if needed.
The Forms Designer has two components: the designer and the forms runtime (formsrt.jar).
The designer is licensed with the LGPL. The runtime has a BSD license.
You may use and distribute forms created by the Forms Designer in commercial applications.
Forms Designer License:  http://www.gnu.org/copyleft/lesser.html
Forms Runtime License: http://www.opensource.org/licenses/bsd-license.php
Advertisement

About Jayanath

My name is Jayanath. I'm from Sri Lanka, which is a small island known as "The Paradise" in the Indian ocean. I live in Guilderland, NY, USA at present and working as a Software Engineer.
This entry was posted in Java Swing, TechTips, Tools and tagged , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s