Many common programs involve graphical user interfaces. We're going to look at Java's libraries for building GUIs, called Swing.
The first class we'll look at is JFrame, which represents a window. Consider the following simple program.
import javax.swing.JFrame; public class EmptyWindow { public static void main(String[] args) { JFrame frame = new JFrame("Empty Window"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.pack(); frame.setVisible(true); } }
This particular program isn't very useful: It just creates a frame and displays it, effectively showing an empty window.
A JFrame has just a few methods worth talking about for now.
JFrame(String title)
(Constructor) Creates a JFrame to represent a
window, with title
in its title bar.
void setDefaultCloseOperation(int value)
Configures how this window behaves when the user clicks
on the close-window button in the title bar. For simple
programs you will want this to be
JFrame.EXIT_ON_CLOSE
.
The default behavior is JFrame.HIDE_ON_CLOSE
;
the window disappears from the screen but the base program
may continue to execute.
void pack()
Resizes this window to fit what it contains.
void setVisible(boolean value)
Configures whether this window is visible on the screen.
Container getContentPane()
Returns an object into which you can place the graphical user interfaces on the screen.
A Container
is an object used for holding components such
as buttons and text fields.
For the moment, we're going to pay attention to just one of
the Container
class's methods.
void add(Component what, Object info)
Inserts what
into this container, using
info
as
information about where the object should be placed.
Some possibilities for the info
parameter to
add
include:
BorderLayout.NORTH |
||||
BorderLayout.WEST |
BorderLayout.CENTER |
BorderLayout.EAST | ||
BorderLayout.SOUTH |
These say where to place the component into the window. You'll notice that this is pretty restricted. There are other ways to lay out components in the window, and we'll get to them eventually.
More significant is the add
method's
Component
parameter. Though Component
is
a class, you'll never write
to create an instance. Instead, you'll create instances of subclasses
of new Component()
Component
, such as JButton
for buttons
or JTextField
for text fields.
For now, the only JButton
documentation we'll
need in for its constructor.
JButton(String label)
(Constructor) Creates a button holding the string
label
.
Another type of component is the text field, where the user can type something.
JTextField(int columns)
(Constructor) Creates a text field, sized to contain
columns
characters.
String getText()
Returns the current text inside this field.
void setText(String text)
Changes the current text of this field to
text
.
Using JButton
and JTextField
,
we can build a program that creates
a window containing a text field sandwiched by two buttons
labeled Increment
and Quit
.
field. We're going to work on making the buttons do
something soon. For the moment, all the program does is display the
components.
import java.awt.BorderLayout; import java.awt.Container; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JTextField; public class ButtonExample { public static void main(String[] args) { JButton incrButton = new JButton("Increment"); JButton quitButton = new JButton("Quit"); JTextField numberField = new JTextField(8); JFrame frame = new JFrame("Button Example"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); Container contents = frame.getContentPane(); contents.add(incrButton, BorderLayout.NORTH); contents.add(numberField, BorderLayout.CENTER); contents.add(quitButton, BorderLayout.SOUTH); frame.pack(); frame.setVisible(true); } }
Now we can work on allowing the user to interact with the window. In order to do this, we need to discuss interfaces. An interface is basically just a set of methods. Any class that hopes to implement the interface must implement all of the methods in the interface's set.
Despite the name, interfaces are not specific to graphical user interfaces! They're a general Java concept, that we just happen to be discussing in the context of graphical user interfaces.
Defining an interface looks a lot like defining a class, except that we can only list instance methods, and none of the instance methods have any bodies. In place of each instance method's body, we just place a semicolon.
For example, we might have defined the following interface for classes that have a position.
public interface Locatable { public double getX(); public double getY(); }
With the interface defined, we will now need classes that
claim to implement the interface. For this, we need to say so
up front using an implements
clause.
Any class that has such anpublic class Ball implements Locatable { //... public class Paddle extends Block implements Locatable { //...
implements
clause has to define all
of the methods defined in the interface. Otherwise, there's a compiler
error. Thus, both Ball
and Paddle
must have getX
and getY
methods;
since the interface says these methods take no parameters and
return a double
, they need to be defined this way
in Ball
and Paddle
too.
What makes interfaces useful is that they are types, too. The following would be completely legitimate.
Locatable loc = new Ball(); System.out.println(loc.getX() + ", " + loc.getY()); loc = new Paddle(); System.out.println(loc.getX() + ", " + loc.getY());
You cannot create an instance of an interface:
would be illegal.new Locatable()
In some sense, with interfaces a class have two superclasses.
Only one of these can be a true Java class, but the type conversion
behavior makes it feel as if both the actual parent class and the
implemented interface are superclasses.
So how can we have our button do something? We're going to have to
use an interface - namely, the ActionListener
interface in the
java.awt.event
package, which includes the following method.
public void actionPerformed(ActionEvent evt)
Called when an action
is performed on a GUI component.
In the case of a button, the action
occurs when the user clicks
on it.
The ActionEvent
parameter specifies information
about the event that has occurred. For the moment, we're just going
to worry about its getSource
method, which returns the
Component
on which the action event was performed.
In order to define what to do for the button, we're going to
need a class that implements this interface. Inside that class's
actionPerformed
method, we'll have some code that say what
to do.
The other step in setting up the program to handle events is to
register the ActionListener object with the component. In our case,
we'll want the addActionListener
method in the JButton
class. It takes takes an ActionListener as an
argument and sets up the button so that, when clicked, it calls the
actionPerformed
method in that ActionListener.
The changes from before are drawn in boldface here.
import java.awt.BorderLayout; import java.awt.Container; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JTextField; public class ListeningExample implements ActionListener { private JButton incr; private JButton quit; private JTextField field; private ListeningExample(JButton i, JButton q, JTextField f) { incr = i; quit = q; field = f; } public void actionPerformed(ActionEvent e) { if(e.getSource() == incr) { int i = Integer.parseInt(field.getText()); field.setText("" + (i + 1)); } else if(e.getSource() == quit) { System.exit(0); } } public static void main(String[] args) { JButton incrButton = new JButton("Increment"); JButton quitButton = new JButton("Quit"); JTextField numberField = new JTextField(8); ListeningExample listener = new ListeningExample(incrButton, quitButton, numberField); incrButton.addActionListener(listener); quitButton.addActionListener(listener); JFrame frame = new JFrame("Listening Example"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); Container contents = frame.getContentPane(); contents.add(incrButton, BorderLayout.NORTH); contents.add(numberField, BorderLayout.CENTER); contents.add(quitButton, BorderLayout.SOUTH); frame.pack(); frame.setVisible(true); } }
The important changes here are that we've added two lines in the
main
method using the addActionListener
method
to register the ListeningExample
object to listen
to actions being performed on its two buttons. That is, when somebody
clicks on either button, it will call the
actionPerformed
method on the constructed ListeningExample
object.
The other important change is the addition of the
actionPerformed
method. In our case, this method first
sees of the components triggered the action — that is, whether
the Increment or Quit button was clicked. It performs an action based
on this.
We don't yet have the ability to build complex windows: We can create components, and we can place them on the north, south, east, or west edge of the window; or we can place it in the window's center. That limits us to five components, and they're not necessarily placed how we want them.
JPanel
classBuilding more complex interfaces requires using the
JPanel
class.
A JPanel
object is an empty graphical component,
but it is a subclass of Container
, so you can add
other components into it.
There are four JPanel methods that are particularly important.
JPanel()
(Constructor) Creates an empty JPanel.
void add(Component what)
Inserts what
into this panel, using the default
placement.
void add(Component what, Object info)
Inserts what
into this panel, using
info
as
information about where the object should be placed.
void setLayout(LayoutManager manager)
Configures this container to use the indicated technique for laying out its components. We'll see how this works soon.
There are several categories of classes involved in creating GUIs using Swing, and we've covered all but the last.
JFrame class
JButton
, JTextField
,
JPanel
)ActionListener
)ActionEvent
)Container
,
JPanel
)FlowLayout
,
BorderLayout
,
GridLayout
)This last category consists
of classes implementing Java's LayoutManager
interface.
The layout classes tell a container class how it should
arrange the components it contains. We can use the
setLayout
method to configure a container, such as
Container
or JPanel
, to use a
different approach for arranging its components.
BorderLayout
We've actually already seen the BorderLayout
class in passing: It's the default
layout for a JFrame
's container. Creating a
BorderLayout
object is simple:
BorderLayout()
(Constructor) Creates a BorderLayout object.
add
method that takes an
info
parameter, and you will pass something like
BorderLayout.NORTH
saying on which border to place the component (or
BorderLayout.CENTER
to place the component in the middle).
The FlowLayout class is even simpler: It places the components in left-to-right order. Each component gets sized to its preferred size. When the components fill the row, they start being placed in the next row.
FlowLayout()
(Constructor) Creates a FlowLayout object.
add
that doesn't take an
info
parameter.
FlowLayout is the default layout for a JPanel.
In the GridLayout class, components are placed left-right, top-down in a strict grid. Each component is resized to fill its grid space.
GridLayout(int rows, int columns)
(Constructor) Creates a GridLayout object, represent the layout strategy with
rows
rows and columns
columns.
As an example illustrating the use of
JPanel
, let's work on a program that creates a
window as at right.
In this program, the user types a temperature into either
blank and presses Enter; the converted temperature then
appears in the other blank.
To accomplish this, the program creates a
JFrame
whose content pane contains two
JPanel
objects, one on its north edge, one on its
south edge. Each JPanel
uses its default
FlowLayout
manager and contains a
JLabel
and a JTextField
.
1 import java.awt.BorderLayout; 2 import java.awt.Container; 3 import java.awt.event.ActionEvent; 4 import java.awt.event.ActionListener; 5 import javax.swing.JButton; 6 import javax.swing.JFrame; 7 import javax.swing.JLabel; 8 import javax.swing.JPanel; 9 import javax.swing.JTextField; 10 11 public class TempConvert implements ActionListener { 12 private JTextField fahr; 13 private JTextField cels; 14 15 private TempConvert(JTextField fahr, JTextField cels) { 16 this.fahr = fahr; this.cels = cels; 17 } 18 19 public void actionPerformed(ActionEvent e) { 20 if(e.getSource() == fahr) { 21 double temp = Double.parseDouble(fahr.getText()); 22 double val = (temp - 32) / 1.8; 23 cels.setText("" + Math.rint(val * 100) / 100); 24 } else if(e.getSource() == cels) { 25 double temp = Double.parseDouble(cels.getText()); 26 double val = 1.8 * temp + 32; 27 fahr.setText("" + Math.rint(val * 100) / 100); 28 } 29 } 30 31 public static void main(String[] args) { 32 JTextField fahrField = new JTextField(8); 33 JTextField celsField = new JTextField(8); 34 35 JPanel fahrPanel = new JPanel(); 36 fahrPanel.add(new JLabel("Fahrenheit:")); 37 fahrPanel.add(fahrField); 38 39 JPanel celsPanel = new JPanel(); 40 celsPanel.add(new JLabel("Celsius:")); 41 celsPanel.add(celsField); 42 43 TempConvert listener = new TempConvert(fahrField, celsField); 44 fahrField.addActionListener(listener); 45 celsField.addActionListener(listener); 46 47 JFrame frame = new JFrame("Temperature Conversion"); 48 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 49 Container contents = frame.getContentPane(); 50 contents.add(fahrPanel, BorderLayout.NORTH); 51 contents.add(celsPanel, BorderLayout.SOUTH); 52 frame.pack(); 53 frame.setVisible(true); 54 } 55 }
JTextField
action listenerNotice how we're defining an ActionListener
for a text field in lines 44 and 45. If you
add an ActionListener
to a text field, its
actionPerformed
method occurs whenever the user types
Enter while the keyboard is focused on the text field.
JLabel
classAnother new thing in this program is its use of
JLabel
, a component that simply displays an
uneditable string to appear in the window.
We use the constructor that takes a String
as its
parameter, which is the string displayed in the window.