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 "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 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:
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 fill the grid position
and 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 SwingSamples.
Create a JFrame Form as the class views.Frame.
Set the layout of Frame to be BorderLayout.
In Properties, set the minimum size to be [250,350].
Drag a List into the Frame (the center).
Set the variable name of the List to be list.
Drag a Panel into the top part.
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 the list properties and clear the
model property.
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…):
Text
Variable Name
Add Item
add
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 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.
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;
}
}
Add this member function to views.Frame:
public void setListCellRenderer(ListCellRenderer rend) {
list.setCellRenderer(rend);
}
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);
}
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.
Edit Frame in source mode.
Add this member function:
public void addGridbagActionListener(ActionListener al) {
gridbag.addActionListener(al);
}
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 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 al) {
record.addActionListener(al);
}
public String getTfText() {
return tf.getText();
}
public Object getComboSelection() {
return combo.getSelectedItem();
}
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.
Edit views.Frame in source mode.
Add this member frunction:
public void addStateActionListener(ActionListener al) {
state.addActionListener(al);
}
Create a new JDialog Form in the views package
with the class name StateDialog.
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:
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 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() };
}
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
Edit Frame in source mode.
Add this member function:
public void addListMouseListener(MouseListener ml) {
list.addMouseListener(ml);
}
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);
}
}
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.
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.
Change to source mode and add the definition of labelText as
a data member in HelloDialog:
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 SwingSamples folder.
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();
}
}
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.
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;
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.