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.
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.
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:
where and what portion of the grid the component occupies
cons.gridx, cons.gridy = int (default = -1 means they aren't used)
cons.gridwidth,
cons.gridheight = int (default = 1) or REMAINDER (remaining row/column)
how the component will occupy the grid position
and how it will resize when the application is resized
where the component is positioned (anchored) within the grid slot
cons.anchor = CENTER, NORTH, SOUTH, EAST, WEST, NORTHEAST, SOUTHEAST, ...
how much space to add around the component:
cons.insets = new Insets(top,right,bottom,left);
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.
Create the new Java Application project SwingComponents.
Create a JFrame Form as the class views.Frame.
Set the layout of Frame to be BorderLayout.
Drag a List into the Frame (the center).
Set the variable name of the List to be list.
Edit the list properties. Clear the
model property and
set the selectionMode to SINGLE.
Drag a Menu Bar from the Swing Menus choices onto the Frame.
Select, right-click and delete the Edit menu.
Select, right-click and change the text (Edit text)
of File menu to be Dialogs.
Add three Menu Items and change the text (Edit text)
and variable names (Change Variable Name…):
TextVariable Name
GridBag gridbag
Set State state
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);
}
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.
Edit Frame in source mode.
Add this member function:
public void addGridbagActionListener(ActionListener listener) {
gridbag.addActionListener(listener);
}
Create a new JDialog Form in the views package
with the class name GridBagDialog.
Set the layout of the dialog to be GridBagLayout.
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.
From the Inspector window, select the GridBagLayout entry,
right-click and select Customize from the popup menu.
In the GridBagLayout Customizer, rearrange the components by dragging
them to the target positions indicated here:
Select the textfield. Set the Fill to Horizontal.
Set the Weight X to 1.
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.
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.
Same for the button, set the Anchor to West.
Select the JLabel3. Set the Anchor to North.
Close the Customizer and take a look at what you have so far.
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:
Set the variable name of the combo box to be combo.
Clear the initial text of the textfield and set its
variable name to tf.
Set the textarea's variable name to ta.
Set the button text to "Record" and change the variable name
to record.
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();
}
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.
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.
Edit views.Frame in source mode.
Add this member frunction:
public void addStateActionListener(ActionListener listener) {
state.addActionListener(listener);
}
Create a new JDialog Form in the views package
with the class name StateDialog.
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:
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.
Edit the Properties of both of the radio buttons.
For the buttonGroup property,
choose buttonGroup1 from the drop-down.
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();
}
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
Edit Frame in source mode.
Add this member function:
public void addListMouseListener(MouseListener listener) {
list.addMouseListener(listener);
}
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);
}
}
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.
Create the new JDialog FormHelpDialog in the views package.
Change the layout of the Dialog to be FlowLayout.
Select the FlowLayout from the Inspector window and set
the Horizontal Gap and Vertical Gap values to be both 20.
Drag a Label onto the form.
Right-click the label and choose Customize Code.
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.
Change to source mode and add the definition of labelText as
a data member in HelloDialog:
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.
Set the variable name of the Panel to be buttons.
Set the layout of the buttons Panel to be FlowLayout.
Select the FlowLayout in the Inspector, right-click to select Properties
and then set the Alignment property to be Right from the drop-down.
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);
}
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.
Copy or move the icons folder into the NetBeans SwingComponents folder.
Create the Java ClassImageButton 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();
}
}
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:
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:
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.
Add these imports
import java.io.*; // if necessary
import java.util.*;
import java.awt.Rectangle;
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.