Swing Components
— print (last updated: Sep 11, 2009) print

Select font size:
Download the SwingComponents.zip archive. The project folder SwingComponents has two subfolders:
src/        source code for the project
icons/      image files for an image button
For expediency you can install the project from existing sources, but it is recommended to go through the step-by-step approach to better appreciate the constructions, in which case only the icons subfolder is necessary.

The construction is divided into 7 sections:
Of these 7 sections the first four are most important. The "Basic Construction" section must be completed, but after that the sections are all independent with the exception that the "Image Button" section requires completion of the "Help Dialog" section.

Discussion

Buttons and Menus

In Swing terms, a button can be thought of as one of the classes derived from AbstractButton which provides most of the useful member functions common to all these objects.
AbstractButton (abstract class)
   |
   |-- JButton 
   |
   |-- JToggleButton
   |      |-- JCheckBox
   |      |-- JRadioButton
   |
   |-- JMenuItem 
          |-- JMenu
          |-- JCheckBoxMenuItem
          |-- JRadioButtonMenuItem
This includes both JButton and JMenuItem objects. All support ActionListener objects. Additionally the classes JToggleButton, JCheckBox, JRadioButton, JCheckBoxMenuItem, JRadioButtonMenuItem maintain a state corresponding to being selected or not. The change of state can be monitored by an ItemListener which activates the itemStateChanged function.

Menus are set up using a JMenuBar object as the basis. This object is added to a JFrame using the setJMenuBar operation. The JMenuBar object adds JMenu objects representing the separate menus. Each JMenu object adds JMenuItem objects to create the actual menu elements.

Exclusivity for stateful buttons

Unlike the situation with HTML form elements, in which radio buttons differ from checkboxes in their ability to do mutually-exclusive selection, Java makes no such distinction. Instead it has a specific ButtonGroup class for that purpose. Any set of checkbox/radio buttons or menu items added to the same ButtonGroup object become mutually exclusive in their selection.

JComboBox

This Swing class effects a drop-down list of items (objects of any type). Like the JList, the contents of the list is controlled by a separate model object, for which the DefaultComboBoxModel type usually suffices. In this example, we use a fixed list of items and so there is no need for a model — we're only interested in determining the selected one.

The JComboBox supports both ActionListener and an ItemListener handlers. The latter behaves much like the ListSelectionListener associated with the JList object and is more likely the one you want to use. The ItemListener must implement the function:
public itemStateChanged(ItemEvent evt)
Like the JList behavior, the listener is called twice when the list item is changed, first for the deselection of one item and second for the selection of a new item. It is usually necessary to filter out the deselection event.

GridBag Layout

A GridBag Layout is the most complex and general layout for organizing components within a grid-like pattern. It can be used to create the other layouts: Grid, Border, Flow. A GridBag layout offers the ability to place items in a grid-like manner by constraining them with dedicated GridBagContraints objects.

We'll see that NetBeans designer effectively hides the dirty work of specifying the constraints on the subcomponents. Nevertheless, it's useful to know what the constaint features are. If you do code this by hand, the development is usually as follows:
GridBagLayout layout = new GridBagLayout();          // name the layout
setLayout(layout);                                   // set the layout
add(component);                                      // add the component
GridBagConstraints cons = new GridBagConstraints();  
cons.PROPERTY = VALUE                                // set properties
...

layout.setConstraints(component, cons);    // constrain component in layout
It all boils down to setting the GrigBagConstraints properties, which have fairly complicated interactions because the effect on a component may have a side effect on the entire row or column containing that component. Here are the the descriptions of common properties:

Basic Construction

The application's frame consists of a JList with Dialogs elicited from menu items. The Dialogs will send their output to the JList entries through the Main controller.
  1. Create the new Java Application project SwingComponents.
  2. Create a JFrame Form as the class views.Frame.
  3. Set the layout of Frame to be BorderLayout.
  4. Drag a List into the Frame (the center).
  5. Set the variable name of the List to be list.
  6. Edit the list properties. Clear the model property and set the selectionMode to SINGLE.
  7. Drag a Menu Bar from the Swing Menus choices onto the Frame.
  8. Select, right-click and delete the Edit menu.
  9. Select, right-click and change the text (Edit text) of File menu to be Dialogs.
  10. Add three Menu Items and change the text (Edit text) and variable names (Change Variable Name…):
    Text        Variable Name
    GridBag     gridbag
    Set State   state
    
  11. Edit Frame in source mode. Add these imports:
    import java.awt.event.*;
    import javax.swing.*;
    
    and add this member function:
      public void setListModel(ListModel m) {
        list.setModel(m);
      }
    
  12. Edit Main, changing it to this starter code. Then test-run.
    package swingcomponents;
    import java.awt.event.*;
    import javax.swing.*;
    import views.*;
    
    public class Main {
      private Frame frame = new Frame();
      private DefaultListModel listmodel = new DefaultListModel();
    
      Main() {
        frame.setBounds(40,40,400,400);
        frame.setVisible(true);
        frame.setListModel(listmodel);
      }
    
      public static void main(String[] args) {
        new Main();
      }
    }
    

GridBag Dialog

The purpose of the GridBag Dialog is to illustrate a GridBag Layout construction with several components whose values can be set and sent back to the controller.
  1. Edit Frame in source mode. Add this member function:
      public void addGridbagActionListener(ActionListener listener) {
        gridbag.addActionListener(listener);
      }
    
  2. Create a new JDialog Form in the views package with the class name GridBagDialog.
  3. Set the layout of the dialog to be GridBagLayout.
  4. Drag onto the dialog form:
    3 Labels, 1 Combo Box, 1 Text Field, 1 Text Area, 1 Button
    The order and positioning is unimportant at this stage.
  5. From the Inspector window, select the GridBagLayout entry, right-click and select Customize from the popup menu.
  6. In the GridBagLayout Customizer, rearrange the components by dragging them to the target positions indicated here:
  7. Select the textfield. Set the Fill to Horizontal. Set the Weight X to 1.
  8. Select the "scrollpane" — this is the scrolled textarea. Set the Fill to Both. Set the Weight X to 1. Set the Weight Y to 1.
  9. Select the combo box. Set the Anchor to West. You can set this from either the properties list or by clicking on the left arrow (i.e., the west) button in the Anchor diagram section.
  10. Same for the button, set the Anchor to West.
  11. Select the JLabel3. Set the Anchor to North.
  12. Close the Customizer and take a look at what you have so far.
  13. Go back into the Customizer and add spacings around the components. These spacings are called insets. Select each component, one-by-one. From the Insets section, click the button to create the desired spacing effects, getting something like this:
  14. Set the variable name of the combo box to be combo.
  15. Clear the initial text of the textfield and set its variable name to tf.
  16. Set the textarea's variable name to ta.
  17. Set the button text to "Record" and change the variable name to record.
  18. Edit GridBagDialog in source mode. Add the import:
    import java.awt.event.*;
    
    and add these member functions:
      public void addRecordActionListener(ActionListener listener) {
        record.addActionListener(listener);
      }
    
      public String getTfText() {
        return tf.getText();
      }
    
      public String getTaText() {
        return ta.getText();
      }
    
      public Object getComboSelection() {
        return combo.getSelectedItem();
      }
    
  19. Edit Main. Add this private data member:
      private GridBagDialog gridbagDialog = new GridBagDialog(frame, true);
    
    Add these statements to the Main constructor and test-run the application.
        frame.addGridbagActionListener(
          new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent evt) {
              gridbagDialog.setVisible(true);
            }
          });
    
        gridbagDialog.addRecordActionListener(
          new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent evt) {
              listmodel.addElement(
                "combo: " + gridbagDialog.getComboSelection() + 
                ", tf: " + gridbagDialog.getTfText() + 
                ", ta: " + gridbagDialog.getTaText()
              );
            }
          });
    

State Dialog

The purpose of the StateDialog is to illustrate how radio buttons, checkboxes and combo boxes can be used to maintain state information within an application.
  1. Edit views.Frame in source mode. Add this member frunction:
      public void addStateActionListener(ActionListener listener) {
        state.addActionListener(listener);
      }
    
  2. Create a new JDialog Form in the views package with the class name StateDialog.
  3. Maintaining the default Free Design layout, drag two Radio Buttons, a Checkbox, and a ComboBox onto the dialog form and resize it so that the outcome looks like something this:
  4. Drag a Button Group object onto the form. It is a non-visible object but you can see it registered in the Other Components section of in the Inspector window.
  5. Edit the Properties of both of the radio buttons. For the buttonGroup property, choose buttonGroup1 from the drop-down.
  6. Edit StateDialog in source mode. Add the import:
    import java.awt.event.*;
    
    and add these member functions:
      public void addCheckListener(ActionListener listener) {
        jCheckBox1.addActionListener(listener);
      }
    
      public void addRadioGroupListener(ItemListener listener) {
        jRadioButton1.addItemListener(listener);
        jRadioButton2.addItemListener(listener);
      }
    
      public void addComboItemListener(ItemListener listener) {
        jComboBox1.addItemListener(listener);
      }
    
      public boolean getCheckState() {
        return jCheckBox1.isSelected();
      }
    
      public boolean[] getRadioState() {
        return new boolean[] {
          jRadioButton1.isSelected(), jRadioButton2.isSelected()
        };
      }
    
      public Object getSelectedComboItem() {
        return jComboBox1.getSelectedItem();
      }
    
  7. Edit Main. Add this private data member:
      private StateDialog stateDialog = new StateDialog(frame, true);
    
    Add these statements to the Main constructor, then test-run.
        frame.addStateActionListener(
          new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent evt) {
              stateDialog.setVisible(true);
            }
          });
    
        stateDialog.addCheckListener(
          new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent evt) {
              listmodel.addElement(
                "check: " + stateDialog.getCheckState());
            }
          });
    
        stateDialog.addRadioGroupListener(
          new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent evt) {
              if (evt.getStateChange() == ItemEvent.DESELECTED) {
                return;
              }
              listmodel.addElement( "radio: " + 
                java.util.Arrays.toString(stateDialog.getRadioState()));
            }
          });
    
        stateDialog.addComboItemListener(new ItemListener(){
          @Override
          public void itemStateChanged(ItemEvent evt) {
            if (evt.getStateChange() == ItemEvent.DESELECTED) {
              return;
            }
            listmodel.addElement("combo: " + 
              stateDialog.getSelectedComboItem());
          }
        });
    

Popup Menu

  1. Edit Frame in source mode. Add this member function:
      public void addListMouseListener(MouseListener listener) {
        list.addMouseListener(listener);
      }
    
  2. Create the Java class ListPopupMenu in the views package.

    views.ListPopupMenu
    package views; import javax.swing.*; import java.awt.event.*; public class ListPopupMenu extends JPopupMenu { public void addClearActionListener(ActionListener al) { clear.addActionListener(al); } private JMenuItem clear = new JMenuItem("Clear List"); public ListPopupMenu() { add(clear); } }
  3. Edit Main. Add this private data member:
      private ListPopupMenu listPopupMenu = new ListPopupMenu();
    
    and add these statements to the Main constructor:
        frame.addListMouseListener(
          new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent evt) {
              if (evt.isPopupTrigger()) {
                listPopupMenu.show(
                  evt.getComponent(), evt.getX(), evt.getY()
                );
              }
            }
          });
          
        listPopupMenu.addClearActionListener(
          new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent evt) {
              listmodel.clear();
            }
          });
    
Test-run by running one of the dialogs to generate content in the list. Then right-click the list to bring up the popup menu with the single choice Clear List which, when activated, will clear the list.

Help Dialog

The Help Dialog is a JDialog which is a good example of one which is not modal, i.e., its modality is set to false. This means that its invocation will not block out the parent frame, making it possible to continue using the GUI with the Help Dialog visible. We will also make this Help Dialog be invoked from a button, which later will become an image icon.
  1. Create the new JDialog Form HelpDialog in the views package.
  2. Change the layout of the Dialog to be FlowLayout.
  3. Select the FlowLayout from the Inspector window and set the Horizontal Gap and Vertical Gap values to be both 20.
  4. Drag a Label onto the form.
  5. Right-click the label and choose Customize Code.
  6. In the Initialization code section, select custom property from the second drop-down. This will highlight the "jLabel1" string. Press enter twice to create a blank white line between the two gray lines. Type labelText in this blank line. Press OK to close the Customizer. You should see that NetBeans registers this change by placing: "<user code>" in place of the label.
  7. Change to source mode and add the definition of labelText as a data member in HelloDialog:
      private String labelText = "<html><h2>Swing Samples Help</h2>" +
        "You probably <b>do not</b><br />need help!";
    
  8. Edit Frame class in source mode. Drag a Panel into the top part. You should see the top part "open up" as the panel goes into position.
  9. Set the variable name of the Panel to be buttons.
  10. Set the layout of the buttons Panel to be FlowLayout.
  11. Select the FlowLayout in the Inspector, right-click to select Properties and then set the Alignment property to be Right from the drop-down.
  12. Edit Frame class in source mode. Add this import
    import java.awt.Component;
    
    and this member function:
      public void addToButtonsPanel(Component c) {
        buttons.add(c);
      }
    
  13. Edit the Main class. Add this member data
      private HelpDialog helpDialog = new HelpDialog(frame,false);
    
    and add this function to the Main class:
      private void addHelpDialogButton1() {
        JButton helpButton = new JButton("Help");
        frame.addToButtonsPanel(helpButton);
    
        helpButton.addActionListener(
          new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
              helpDialog.setVisible(true);
            }
          });
      }
    
    and call this function in the Main constructor before the frame.setVisible(true) call:
        addHelpDialogButton1();
        frame.setVisible(true);
    

Image Button

Java Swing classes do not provide a direct way to make an image act like a button, and so it has to be constructed "from scratch." The image button uses (up to) three related images, reflecting the three common states of activation
seen when mouse is not over the button seen when mouse is over the button and not pressed seen when mouse is over the button and button pressed
The idea is that a JPanel can be made to appear as an image simply by redefining the public paint function.
  1. Copy or move the icons folder into the NetBeans SwingComponents folder.
  2. Create the Java Class ImageButton in the views folder:

    views.ImageButton
    package views; import java.awt.*; import java.awt.event.*; import java.awt.image.*; import javax.swing.*; public class ImageButton extends JPanel { private ActionListener actionListener = null; private boolean down = false, over = false, enabled = true; private boolean hasOver = false, hasDown = false; private Image upImage, downImage, overImage, disabledImage; public ImageButton(ImageIcon up) { this(up, null, null); } public ImageButton(ImageIcon up, ImageIcon down, ImageIcon over) { if (up == null) throw new UnsupportedOperationException("must have up ImageIcon"); upImage = up.getImage(); if (down != null) { hasDown = true; downImage = down.getImage(); } if (over != null) { hasOver = true; overImage = over.getImage(); } RGBImageFilter disableFilter = new RGBImageFilter() { @Override public int filterRGB(int x, int y, int rgb) { return (rgb & ~0xff000000) | 0x80000000; } }; disabledImage = createImage(new FilteredImageSource(upImage.getSource(), disableFilter)); addMouseListener(mouselistener); addMouseMotionListener(mousemotionlistener); } private MouseListener mouselistener = new MouseAdapter() { @Override public void mousePressed(MouseEvent evt) { down = true; repaint(); } @Override public void mouseEntered(MouseEvent evt) { over = true; repaint(); } @Override public void mouseExited(MouseEvent evt) { over = false; repaint(); } @Override public void mouseReleased(MouseEvent evt) { down = false; repaint(); if (over && evt.getButton() == MouseEvent.BUTTON1) { ActionEvent ae = new ActionEvent(evt.getComponent(), 0, "click"); if (actionListener != null) { actionListener.actionPerformed(ae); } } } }; private MouseMotionListener mousemotionlistener = new MouseMotionAdapter() { @Override public void mouseDragged(MouseEvent evt) { if (over) { down = true; } else { down = false; repaint(); } } }; @Override public void paint(Graphics g) { if (!enabled) { g.drawImage(disabledImage, 0, 0, this); return; } if (hasDown && down) { g.drawImage(downImage, 0, 0, this); } else if (hasOver && over) { g.drawImage(overImage, 0, 0, this); } else { g.drawImage(upImage, 0, 0, this); } } @Override public void setEnabled(boolean enabled) { this.enabled = enabled; if (!enabled) { removeMouseListener(mouselistener); removeMouseMotionListener(mousemotionlistener); } else { addMouseListener(mouselistener); addMouseMotionListener(mousemotionlistener); } repaint(); } @Override public boolean isEnabled() { return enabled; } public void addActionListener(ActionListener l) { actionListener = AWTEventMulticaster.add(actionListener, l); } public void removeActionListener(ActionListener l) { actionListener = AWTEventMulticaster.remove(actionListener, l); } @Override public Dimension getPreferredSize() { return (new Dimension(upImage.getWidth(this), upImage.getHeight(this))); } @Override public Dimension getMinimumSize() { return getPreferredSize(); } }
  3. Edit the Main class. Add the following imports:
    import java.io.*;
    
    and this member function to the Main class:
      private void addHelpDialogButton2() {
        String pathToIcons = System.getProperty("user.dir")
          + File.separator + "icons" +  File.separator;
    
        ImageIcon upIcon = new ImageIcon(pathToIcons + "help1.gif");
        ImageIcon downIcon = new ImageIcon(pathToIcons + "help2.gif");
        ImageIcon overIcon = new ImageIcon(pathToIcons + "help3.gif");
    
        ImageButton helpButton = new ImageButton(upIcon, downIcon, overIcon);
    
        helpButton.setToolTipText("Help");
        frame.addToButtonsPanel(helpButton);
    
        helpButton.addActionListener(
          new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent evt) {
              helpDialog.setVisible(true);
            }
          });
      }
    
    and call this function in the Main constructor before the frame.setVisible(true) call, commenting out the previous version:
        //addHelpDialogButton1();
        addHelpDialogButton2();
        frame.setVisible(true);
    
Test-run the application.

Property Persistence

By a persistent property of an application we mean one which is not set initially, is set by the user before the application is closed and is reinstated when the application is reopened. An application maintains persistent properties by storing them in one or more related files or other data storage (like a database).

In this example, we will create the Java serialized object, SwingSampleSettings, to be the single file holding persistent information. The only persistent information we're interested in are the Frame bounds, as indicated by the functions:
Rectangle getBounds();
void setBounds(Rectangle bounds);
The file SwingSampleSettings, stored in the user's home directory, will be a serialized Map<String,Object> object. The string key "bounds" will associate with the Rectangle representing the Frame's bounds. This will be stored into the object and read upon startup.

The changes are all to the Main class.
  1. Add these imports
    import java.io.*;    // if necessary
    import java.util.*;
    import java.awt.Rectangle;
    
  2. Add this member data and functions:
      private File settingsFile = new File(
        System.getProperty("user.home") + File.separator + "SwingComponentsSettings");
    
      private Map<String, Object> readSettings() {
        Map<String, Object> settings = null;
        try {
          if (settingsFile.exists()) {
            ObjectInputStream oistr =
              new ObjectInputStream(new FileInputStream(settingsFile));
            settings = (Map<String, Object>) oistr.readObject();
            oistr.close();
          } else {
            settings = new HashMap<String, Object>();
          }
        } catch (Exception x) {
          x.printStackTrace();
          System.exit(1);
        }
        return settings;
      }
    
      private void writeSettings(Map<String, Object> settings) {
        try {
          ObjectOutputStream oostr =
            new ObjectOutputStream(new FileOutputStream(settingsFile));
          oostr.writeObject(settings);
          oostr.close();
        } catch (IOException x) {
          x.printStackTrace();
          System.exit(1);
        }
      }
    
  3. In Main(), replace the statement
        frame.setBounds(40, 40, 400, 400);
    
    by:
        final Map<String,Object> settings = readSettings();
    
        Rectangle bounds = (Rectangle) settings.get("bounds");
        if (bounds == null)
          frame.setBounds(40, 40, 400, 400);
        else
          frame.setBounds(bounds);
          
        frame.addWindowListener(
          new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent evt) {
              settings.put("bounds", frame.getBounds());
              writeSettings(settings);
            }
          });
    
        frame.setVisible(true);
    
Test-run the application, move and/or resize the Frame, close it and reopen to discover the configuration has been remembered. Check your home directory for the presence of the SwingComponentsSettings file.


© Robert M. Kline