Swing Samples
— print (last updated: Feb 6, 2009) print

Select font size:
Download the SwingSamples.zip archive. The project folder SwingSamples 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.

The construction is divided into 8 sections:
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 among state changes

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.

Dialogs

The JDialog class is the Swing class used to generating a popup with all the capabilites of the original JFrame. The primary difference is that a JDialog must be "attached" to the original JFrame GUI. In particular, the constructor must initialize the superclass with the JFrame parent in one of several ways:
class MyDialog extends JDialog {
  MyDialog() { 
    super(parent);
    ...
}
The dialog is "popped up" and "popped down" using the setVisible call with values true and false, respectively.

The other key feature of a JDialog is its boolean modal property. This property expresses whether the dialog should "block" actions in the original GUI. A modality of true means to block actions, a modality of false means to let the actions of the GUI continue. One can set the modality through the constructor via:
super(parent, <true or false>);
or in a separate statement
setModal(<true or false>);

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 functions like a Flow layout unless the components are constrained using GridBagContraints objects. The setup done by hand is usually like this (Java accepts either spelling GridBagLayout or GridbagLayout):
GridBagLayout layout = new GridBagLayout();        // name the layout
setLayout(layout);                                 // set the layout
add(component);                                    // add the component
GridBagConstraints cons = new GridBagConstraints();  
cons.PROPERTY = VALUE
layout.setConstraints(component, cons);    // constrain component in layout
It all boils down to figuring out how to specifiy the GrigBagConstraints properties, which have fairly complicated interactions because the effect on a component may have an unexpected effect on the entire row or column containing that component. Here are the common properties that I use:

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 SwingSamples.
  2. Create a JFrame Form as the class views.Frame.
  3. Set the layout of Frame to be BorderLayout.
  4. In Properties, set the minimum size to be [250,350].
  5. Drag a List into the Frame (the center).
  6. Set the variable name of the List to be list.
  7. Drag a Panel into the top part.
  8. Set the variable name of the Panel to be buttons.
  9. Set the layout of the buttons Panel to be FlowLayout.
  10. Select the FlowLayout in the Inspector, right-click to select Properties and then set the Alignment property to be Right from the drop-down.
  11. Edit the list properties and clear the model property.
  12. Drag a Menu Bar from the Swing Menus choices onto the Frame.
  13. Select, right-click and delete the Edit menu.
  14. Select, right-click and change the text (Edit text) of File menu to be Dialogs.
  15. Add three Menu Items and change the text (Edit text) and variable names (Change Variable Name…):
    Text Variable Name
    Add Item add
    GridBag gridbag
    Set State state
  16. 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);
      }
    
  17. Edit Main, changing it to this starter code. Then test-run.
    package swingsamples;
    import java.awt.event.*;
    import javax.swing.*;
    import views.*;
    
    public class Main {
      private Frame frame = new Frame();
      private DefaultListModel listmodel = new DefaultListModel();
    
      Main() {
        frame.setVisible(true);
        frame.setListModel(listmodel);
      }
    
      public static void main(String[] args) {
        new Main();
      }
    }
    

Set the cell renderer

The purpose of the cell renderer is to accurately render multiline strings as list cell contents. The default rendering of string contents effectively disregards newlines.
  1. Create the following classes in the swingsamples controller package:

    swingsamples/MultiLineListCell.java
    package swingsamples; import javax.swing.*; import java.awt.*; public class MultiLineListCell extends JPanel { private Color fg, bg; private Font font; private String[] lines; public MultiLineListCell(Color fg, Color bg, Font font, String[] lines) { this.fg = fg; this.bg = bg; this.font = font; this.lines = lines; } @Override public void paintComponent(Graphics g) { super.paintComponent(g); g.setFont(font); g.setColor(bg); int width = getWidth(), height = getHeight(); g.fillRect(0, 0, width, height); g.setColor(fg); int asc = g.getFontMetrics().getAscent(); for (int i = 0; i < lines.length; ++i) { g.drawString(lines[i], 0, (i + 1) * asc); } g.drawLine(0, height-1, width, height-1); } @Override public Dimension getPreferredSize() { int asc = getGraphics().getFontMetrics().getAscent(); return new Dimension(getWidth(), asc * lines.length + asc/2); } }

    swingsamples/MultiLineRenderer.java
    package swingsamples; import javax.swing.*; import java.awt.*; public class MultiLineRenderer implements ListCellRenderer { @Override public Component getListCellRendererComponent(final JList list, final Object value, final int index, final boolean isSelected, final boolean cellHasFocus) { String s = (String) value; String[] lines = s.split("\\n"); Color fg = isSelected?list.getSelectionForeground():list.getForeground(); Color bg = isSelected?list.getSelectionBackground():list.getBackground(); Font font = list.getFont(); MultiLineListCell cell = new MultiLineListCell(fg, bg, font, lines); return cell; } }
  2. Add this member function to views.Frame:
      public void setListCellRenderer(ListCellRenderer rend) {
        list.setCellRenderer(rend);
      }
    
  3. Add this statement to the Main constructor:
        frame.setListCellRenderer(new MultiLineRenderer());
    

Add Item Dialog

The Add Item Dialog provides the ability to generate a paragraph and have it entered into the list.
  1. Edit Frame in source mode. Add this member function:
      public void addAddActionListener(ActionListener al) {
        add.addActionListener(al);
      }
    
  2. Create a new JDialog Form in the views package with the class name AddDialog.
  3. Give the dialog the BorderLayout layout.
  4. Drag a Text Area onto the dialog (the center)
  5. Change the variable name of the Text Area to be display.
  6. In the Properties of display, make columns=30 and rows=10.
  7. Drag a Panel onto the bottom of the dialog.
  8. Drag two buttons into the Panel.
  9. Make the Panel have FlowLayout layout.
  10. Give the buttons these Text/Variable Name combinations:
    text: Add It    variable name: addit
    text: Discard   variable name: discard
    
  11. Edit AddDialog in source mode. Add this import:
    import java.awt.event.*;
    
    and these member functions:
      public void setDisplayText(String content) { 
        display.setText(content);
      }
    
      public String getDisplayText() { return display.getText(); }
    
      public void addDiscardActionListener(ActionListener al) {
        discard.addActionListener(al);
      }
    
      public void addAdditActionListener(ActionListener al) {
        addit.addActionListener(al);
      }
    
  12. Edit Main. Add this member variable:
      private AddDialog addDialog = new AddDialog(frame, true);
    
    and add this code to the Main constructor:
        frame.addAddActionListener(
          new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent evt) {
              addDialog.setVisible(true);
              addDialog.setDisplayText("");
            }
          });
    
        addDialog.addAdditActionListener(
          new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent evt) {
              listmodel.addElement(addDialog.getDisplayText());
              addDialog.setVisible(false);
            }
          });
    
        addDialog.addDiscardActionListener(
          new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent evt) {
              addDialog.setVisible(false);
            }
          });
    
        addDialog.addWindowListener(
          new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent evt) {
              if (!addDialog.getDisplayText().equals("")) {
                int response = JOptionPane.showConfirmDialog(
                  frame, "Add Check", "Do you want to add it?",
                  JOptionPane.YES_NO_OPTION);
                if (response == JOptionPane.YES_OPTION) {
                  listmodel.addElement(addDialog.getDisplayText());
                }
              }
              addDialog.setVisible(false);
            }
          });
    
Test-run the application.

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 al) {
        gridbag.addActionListener(al);
      }
    
  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 button text to "Record" and change the variable name to record.
  17. Edit GridBagDialog in source mode. Add the import:
    import java.awt.event.*;
    
    and add these member functions:
      public void addRecordActionListener(ActionListener al) {
        record.addActionListener(al);
      }
    
      public String getTfText() {
        return tf.getText();
      }
    
      public Object getComboSelection() {
        return combo.getSelectedItem();
      }
    
  18. Edit Main. Add this private data member:
      private GridBagDialog gridbagDialog = new GridBagDialog(frame, true);
    
    and these statements to the Main constructor:
        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()
                + "\n"
                + "tf: " + gridbagDialog.getTfText()
                );
            }
          });
    
Test-run the application.

State Dialog

The purpose of the State Dialog is to illustrate radio button and checkbox usage and the maintenance of state information by these components.
  1. Edit views.Frame in source mode. Add this member frunction:
      public void addStateActionListener(ActionListener al) {
        state.addActionListener(al);
      }
    
  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 and a Checkbox onto the dialog form and resize it so that the outcome looks like 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 al) {
        jCheckBox1.addActionListener(al);
      }
    
      public void addRadioListener(ItemListener il) {
        jRadioButton1.addItemListener(il);
        jRadioButton2.addItemListener(il);
      }
    
      public boolean getCheckState() {
        return jCheckBox1.isSelected();
      }
    
      public boolean[] getRadioState() {
        return
          new boolean[]{ jRadioButton1.isSelected(), jRadioButton2.isSelected() };
      }
    
  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(
                "radio: " + java.util.Arrays.toString(stateDialog.getRadioState())
                + "\n" + "check: " + stateDialog.getCheckState());
            }
          });
    
        stateDialog.addRadioListener(
          new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent evt) {
              if (evt.getStateChange() == ItemEvent.DESELECTED) {
                return;
              }
              listmodel.addElement(
                "radio: " + java.util.Arrays.toString(stateDialog.getRadioState())
                + "\n" + "check: " + stateDialog.getCheckState());
            }
          });
    

Popup Menu

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

    views/ListPopupMenu.java
    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:
        listPopupMenu.addClearActionListener(
          new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent evt) {
              listmodel.clear();
            }
          });
    
        frame.addListMouseListener(
          new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent evt) {
              if (evt.isPopupTrigger()) {
                listPopupMenu.show(evt.getComponent(), evt.getX(), evt.getY());
              }
            }
          });
    
Test-run by running one of the other three 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.
  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.
  7. Change to source mode and add the definition of labelText as a data member in HelloDialog:
      String labelText = "<html><h2>Swing Samples Help</h2>" +
        "You probably <b>do not</b><br />need help!";
    
  8. Edit the Frame class. Add this import
    import java.awt.Component;
    
    and this member function:
      public void addToButtonsPanel(Component c) {
        buttons.add(c);
      }
    
  9. 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 SwingSamples folder.
  2. Install the following class:

    views/ImageButton.java
    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.

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:
      private File settingsFile = new File(
        System.getProperty("user.home") + File.separator + "SwingSamplesSettings");
    
  3. Add these member functions:
      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);
        }
      }
    
  4. Add these statements before the frame.setVisible(true) call:
        final Map<String,Object> settings = readSettings();
    
        Rectangle bounds = (Rectangle) settings.get("bounds");
        if (bounds != null)
          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 SwingSamplesSettings file.


© Robert M. Kline