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.
- The generated code should not be edited and we have to live with what ever the code that IDE throws at us
- 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.
- 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
- Download and unzip the Abeille form designer and run the designer.jar
- Create a new project by selecting “File –> New Project”
- Go to Tools–>Preferences and click the Store forms as XML check box on the Projects tab.
- Create a new form by selecting “File–>New Form”
- 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.
- Change the size of the columns and rows of the grid by dragging from the two pegs provided in each axis (figure 1)
- 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
- Name the components accordingly as we are going to use this name to reference the components in the code
- 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.
- Cleaner code, NO IDE generated code intertwined with the application code.
- We have the liberty to swap any component in the GUI panel with any desired component
- 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
- 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.
- 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.
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
- JGoodies libraries
- 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




