Applications that interact with an end user typically have a graphical user interface (GUI) for presenting information and receiving inputs from users. A well designed GUI goes a long way towards making the user experience positive and productive, and to that end, the Java programming language provides Project Swing and the Abstract Window Toolkit (AWT), which are complete libraries for designing, building, and powering GUIs for applets and applications.
Lesson 6 in Essentials of the Java Programming Language: A Hands-On Guide shows how to write a simple Project Swing application that saves data to a file and reads it back again. In that lesson, the user interface consists of simple text fields and buttons. Most GUI applications, however, have more involved user interfaces to support a wider range of activities and functionality.
This article shows you how to enhance the Lesson 6 example to use menus instead of buttons, and employ layout managers to control how user interface components are arranged on different parts of the display. It also introduces simple Java 2D features to show you how to add styled text and colored geometric shapes to the display. The Java 2D API has many easy-to-use classes for enhancing the appearance and usability of your applications.
The application is a very scaled down text editor. Its user interface consists of a File menu, a message area on the left, and an editable text area with scrollbars on the right. The user enters text into the editable text area and selects Save Text to File to save the text to a file. After the save operation, the text remains in the editable text area for further editing. The user can select Get Text from File to load text from the file or Clear Display to clear the text area and any messages appearing in the message area.
The figure shows the initial screen with the File menu pulled down. When the application starts up, the Ready message appears in the message area to let you know the application is ready to use.
The code for the example application consists of two classes, the
FileIO
class and the Painter
class. The
FileIO
class has the main
method and is the
class to launch to run the application. For convenience, both classes
are in the FileIO.java
class file, but the application
will work if you move the Painter
class to its own file
in the same directory where you have the FileIO.java
class
file.
FileIO.java file: Click to view and Shift-Click to download.
To compile and run the example, execute the compiler and interpreter commands
as follows. If you moved the Painter
class to its own file, it
is automatically compiled when you compile the FileIO
class because
the FileIO
class references the Painter
class.
javac FileIO.java java FileIO
The FileIO
class creates the user interface and manages user
inputs. The user interface consists of the File menu,
an editable text area with scrollbars, and a message area.
A menu system for an application consists of a menu bar
(JMenuBar
), the menus (JMenu
), and menu
items (JMenuItem
). Menu items are added to menus, and
menus are added to the menu bar.
The menu bar and file menu objects are declared and created in the constructor where the file menu is added to the menu bar. Menus appear in the menu bar going left to right in the order you add them.
The menu item objects are declared as instance variables to make them available
to the actionPerformed
method for event processing. The menu items
are added to the File menu, and the File menu added to the menu bar in the constructor.
The File menu and menu item constructors take a string that is the menu or menu
item label.
JMenuItem savetext, gettext, cleardisplay; . . . FileIO() { //Begin Constructor JMenuBar menubar = new JMenuBar(); setJMenuBar(menubar); JMenu filemenu = new JMenu("File"); menubar.add(filemenu); savetext = new JMenuItem("Save Text to File"); gettext = new JMenuItem("Get Text from File"); cleardisplay = new JMenuItem("Clear Display"); filemenu.add(savetext); filemenu.add(gettext); filemenu.add(cleardisplay); . . . }
The FileIO
class extends the JFrame
class, and
the JFrame
class has a setJMenuBar
method for
setting the menu bar for the JFrame
object. The ability
to set the menu bar means you can have more than one menu bar and
change which menu bar appears on the frame based on contextual user input.
JMenuBar menubar = new JMenuBar(); setJMenuBar(menubar);
The application's screen area (frame) is divided into two areas and each area contains a panel. Panel 1 on the left contains the message area, and Panel 2 on the right contains the editable text area.
A layout manager is used to divide the frame into two areas. In this
example a GridLayout
is used to give the frame a two-column,
one-row display. The layout manager is not set directly on the frame, but
on the frame's content pane. The content pane provides functionality that
allows different types of components to work together in Project Swing.
The panels are then created and panel1
added first so
it appears in the first column, followed by panel2
in
the second column.
getContentPane().setLayout(new GridLayout(1,2)); panel1 = new JPanel(); panel1.setLayout(new BorderLayout()); panel1.setBackground(Color.white); getContentPane().add(panel1); panel2 = new JPanel(); panel2.setLayout(new FlowLayout()); panel2.setBackground(Color.white); getContentPane().add(panel2);
Each panel has its own layout manager to control the display of its own components. The Border layout used for Panel 1 consists of five fixed areas: north, south, east, west, and center. You do not have to put a component in every area, and in this example only the center and south areas are used to display messages to the user.
The flow layout used for panel 2 places components in rows according to the width of the panel and the number and size of the components. Components are repositioned when the frame is resized. In this example, Panel 2 has one component (the editable text area) and the flow layout works just fine.
The message area displays application messages to the user. Application messages are either error or notification messages. Notification messages tell the user the application is ready, text was saved to the file, or text was read from the file. Notification messages appear in a panel that is added to the center area in the Panel 1 border layout.
Error messages tell the user the file read or write operation was unsuccessful, there is no text to save, or the file could not be opened or closed. Error messages appear in a label component that is added to the south area in the Panel 1 border layout.
The application uses styled text to display notification messages.
Styled text is a Java 2D API that lets you create interesting and
interactive styled text in any language supported by the Unicode
character set. You can draw styled text directly onto a panel
component by extending the JPanel
class and implementing
the paintComponent
method to draw styled text in
the panel the way you want it drawn.
Whenever you extend another class, the new class inherits the methods
and fields (and resulting behavior) of its parent class. In the case of
JPanel
, the paintComponent
method draws the
panel. If you extend JPanel
and override this method,
you can customize the drawing behavior.
In this example, the Painter
class extends
JPanel
and implements the paintComponent
class to use a color and text string to draw styled text onto the
panel. An instance of the Painter
class is created and
added to the center area of Panel 1. You can see the code for
and get more information on the Painter
class in
Painter Class Description below.
//Create message area from color and text string panel1paint = new Painter(Color.black, "Ready"); Dimension dimension = new Dimension(); dimension.setSize(200, 25); panel1paint.setSize(dimension); //Add message area to Panel 1 panel1.add(BorderLayout.CENTER, panel1paint); //Add message label to panel 1 messlab1 = new JLabel(); panel1.add(BorderLayout.SOUTH, messlab1);
The editable text area is an instance of the JTextArea
class. Its setFont
method sets the style for text
displayed and entered into the text area. In this case, the style
is 12 point serif italics. The setLineWrap
method
is set to true
so longer text wraps to the next line.
Text areas are editable by default, but for clarity the call
to setEditable
with a value of true
is included. If you want to make the text area uneditable, just
change this parameter to false
and recompile.
The editable text area is in a scroll pane (areaScrollPane
)
so if there is more text than will fit in the display, the user can scroll
to view it all. The scroll pane has a vertical scrollbar that displays
at all times whether there is more text than fits in the display
or not.
You can change the settings to use a horizontal scrollbar or
display the vertical scrollbar only when needed by changing the
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS
setting.
The JScrollPane
class implements the
ScrollPaneConstants
interface, which provides constants
(static fields) for specifying scrollbar settings.
The areaScrollPane
size is set to 150 pixels by 150 pixels,
and has a titled border. The scroll pane containing the editable
text area is added to Panel 2.
//Create text area for panel 2 disptext = new JTextArea(); disptext.setFont(new Font("Serif", Font.ITALIC, 12)); disptext.setLineWrap(true); disptext.setWrapStyleWord(true); disptext.setEditable(true); JScrollPane areaScrollPane = new JScrollPane(disptext); areaScrollPane.setVerticalScrollBarPolicy( JScrollPane.VERRTICAL_SCROLLBAR_ALWAYS); areaScrollPane.setPreferredSize( new Dimension(200, 175)); areaScrollPane.setBorder( BorderFactory.createTitledBorder( "Enter or Edit Text:")); panel2.add(areaScrollPane);
The FileIO
class implements the ActionListener
interface so it can listen for action events. To listen for menu item
events, the FileIO
class is added as an action listener to
the three menu items by calling the addActionListener
methods
on the menu item objects and passing this
as the parameter.
The this
parameter refers to this
(the FileIO
) class.
//This class listens for menu item events savetext.addActionListener(this); gettext.addActionListener(this); cleardisplay.addActionListener(this); } //End Constructor
All classes that implement the ActionListener
interface must provide an implementation for its
actionPerformed
method. When the user selects
one of the menu items at run time, the Java platform passes
an ActionEvent
object to this method.
A series of if
statements are used to find out
which menu item was selected and the appropriate action
taken based on the findings.
clearDisplay
method is called to clear error message, notification message,
or editable text from the display.
protected void clearDisplay(){ messlab1.setText(""); panel1paint.setColor(Color.black); panel1paint.setText("Ready"); panel1paint.repaint(); disptext.setText(""); } public void actionPerformed( ActionEvent event){ Object source = event.getSource(); String returned = null; FileInputStream in=null; FileOutputStream out=null; if(source == cleardisplay) { clearDisplay(); } if(source == savetext) { returned = disptext.getText(); if(returned != null) { //Write to file try { byte b[] = returned.getBytes(); String outputFileName = System.getProperty("user.home", File.separatorChar + "home" + File.separatorChar + "zelda") + File.separatorChar + "text.txt"; out = new FileOutputStream( outputFileName); out.write(b); panel1paint.setText( " Text successfully saved."); panel1paint.setColor(Color.blue); panel1paint.repaint(); disptext.setText(returned); } catch(java.io.IOException e) { messlab1.setText( "Cannot write to text.txt"); } finally { if(out != null) { try { out.close(); } catch(java.io.IOException e) { messlab1.setText( "Cannot close file"); } } } } else { messlab1.setText("No Text to Save"); } } if(source == gettext) { //Read from file try{ String inputFileName = System.getProperty(" user.home", File.separatorChar + "home" + File.separatorChar + "zelda") + File.separatorChar + "text.txt"; File inputFile = new File(inputFileName); in = new FileInputStream(inputFile); byte bt[] = new byte[(int)inputFile.length()]; in.read(bt); String s = new String(bt); disptext.setText(s); panel1paint.setText( " Text read from file: "); panel1paint.setColor(Color.blue); panel1paint.repaint(); } catch(java.io.IOException e) { messlab1.setText( "Cannot read from text.txt"); } finally { if(in != null) { try { in.close(); } catch(java.io.IOException e) { messlab1.setText("Cannot close file"); } } } } }
The above code used a call to System.getProperty
to create the pathname to the file in the user's home directory.
The System class maintains a set of properties that define
attributes of the current working environment. When the Java platform
starts, system properties are initialized with information about the
runtime environment including the current user, Java platform version,
and the character used to separate components of a file name
(File.separatorChar
).
The call to System.getProperty
uses the keyword
user.home
to get the user's home directory and supplies
the default value File.separatorChar + "home" + File.separatorChar +
"monicap"
) in case no value is found for this key.
The above code used the java.io.File.separatorChar
variable to
construct the directory pathname. This variable is initialized to contain the
file separator value stored in the file.separator
system property
and gives you a way to construct platform-independent pathnames.
For example, the pathname /home/monicap/text.txt
for Unix and
\home\monicap\text.txt
for Windows are both represented as
File.separatorChar + "home" + File.separatorChar + "monicap" +
File.separatorChar + "text.txt"
in a platform-independent construction.
The Painter
class extends JPanel
and implements
the paintComponent
method to draw styled text from a
specified color and text string. The paintComponent
method
is not called directly by the application, but gets called as a result
of an application call to the Painter
class
repaint
method or constructor. The
Painter
class inherits the paintComponent
method
from the JPanel
class, and the repaint
method from
the Component
class.
By overriding the paintComponent
method, the Painter
class defines how its instances are drawn. The paintComponent
method is implemented in the JPanel
class to paint (or draw) the
panel component. In the Painter
class the paintComponent
method draws the panel with a styled text string using the specified color.
The color and text string information are passed to a Painter
instance in the constructor and assigned to instance variables.
The Painter
class has accessor methods to get and set the
color and string values.
To do the drawing, the paintComponent
method creates a 2D graphics
context. All 2D graphics objects including styled text and geometric shapes are
drawn into a 2D graphics context, which provides control over
such things as geometry coordinate transformation, color, and text layout.
Because the example draws styled text into the 2D graphics context,
the setRenderingHints
method is called on the graphics
context to turn antialiasing
on and to use appropriate rendering algorithms. These settings improve the
quality of the final drawing.
After setting rendering hints, the background color for any panel created
from the Painter
class is set to white by calling
g2.fillRect
. In this example, the panel1paint
instance is created from the Painter
class and added to the
center area on Panel 1.
The white fills all areas of Panel 1 because the
central area expands to cover those areas that do not have components
(north, east, and west), and the paintComponent
method for
the label added to the south area draws a white background by default.
If you want to use background colors to dress up a user interface,
you can extend any component and implement its paintComponent
method to use whatever color you want.
class Painter extends JPanel { Color c; String s; Painter(Color c, String s) { this.s = s; this.c = c; } protected void setColor(Color c) { this.c = c; } protected Color getColor() { return this.c; } protected void setText(String s) { this.s = s; } protected String getText() { return this.s; } public void paintComponent(Graphics g) { Graphics2D g2; g2 = (Graphics2D) g; g2.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.setRenderingHint( RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); //Make background white g2.setColor(Color.white); g2.fillRect(0, 0, getSize( ).width -1, getSize().height -1); //Set font rendering context and font FontRenderContext frc = g2.getFontRenderContext(); Font f = new Font("Helvetica", Font.PLAIN, 15); //Create styled text from font and string TextLayout tl = new TextLayout(s, f, frc); //Get the size of the drawing area Dimension theSize = getSize(); //Set the 2D graphics context color for drawing the text g2.setColor(c); //Draw the text into the drawing area tl.draw(g2, theSize.width/30, theSize.height/2); //Put a blue box around the styled text //unless the text string is "Ready" if(this.s != "Ready") { g2.drawRect(0, 0, getSize( ).width -1, getSize().height -1); } } }
The TextLayout
class defines styled text, which is
an immutable graphical representation of styled character data.
Styled text is created from a string, font, and font render context,
and rendered with its draw
method.
A font render context is a container for the information needed to
correctly measure text. The measurement of text can vary because of
rules that map outlines to pixels, and rendering hints provided by
an application.
The call to tl.draw
renders the styled text at the
specified location in the specified 2D graphics context. In this
example, that location is the height of the graphics context divided
by 30 and the width divided by 2.
This example draws a blue rectangle around the graphics context
in the event the text string contains anything other than
Ready
.
The following figure shows how the application looks with the blue rectangle and text typed into the editable text area. The sequence of events to bring up this window are:
panel1paint
object
begins and ends.
If you are familiar with both AWT and Project Swing, you probably know that
the platform does not stop you from mixing AWT and Project Swing classes
in the same application. You might also be aware that the AWT provides a
Canvas
class that represents a blank rectangular area of
the screen onto which the application can draw. You might be wondering why
the Painter
class extends the JPanel
class and not
the Canvas
class.
Well, the short answer is that an early version of the example did exactly that
and ran into a problem. The problem was you could not see the extended
File menu without drastically reducing the size of the application because
the File menu drew behind the canvas. The cause of the problem is the
Canvas
class defines heavyweight components, and the JMenu
,
JMenuBar
, and JMenuItem
classes all define
lightweight components. Heavyweight components always draw over lightweight
components.
So, the Canvas
obscured the expanded File menu by drawing over
it. However, if you use only lightweight components, the File menu always
draws on top. By making the Painter
class extend either
JPanel
or JComponent
, which are classes that define
lightweight components, the expanded menu draws correctly on top.
For performance and behavior reasons, it is generally inadvisable to unnecessarily mix heavyweight and lightweight components. Your application user interface should use either all AWT or all Project Swing components. The following discussion excerpted from Advanced Programming with the Java 2 Platform offers some insight into why.
All components in Project Swing except JApplet
,
JDialog
, JFrame
, and JWindow
are
lightweight components. Lightweight components, unlike their heavyweight AWT
counterparts, do not depend on the local windowing toolkit. For example, a
heavyweight java.awt.Button
running on the Java platform for
the Unix operating system maps to a real Motif button. In this relationship,
the Motif button is called the peer to the java.awt.Button
.
If you create two java.awt.Buttons
in an application, two peers and hence
two Motif buttons are also created. The Java platform communicates with the Motif buttons
using the Java Native Interface (JNI). For each and every component added to the
application, there is additional overhead tied to the local windowing system, which is why these
components are called heavyweight.
Lightweight components are termed peerless and emulate the local window system
components. A lightweight javax.swing.JButton
is represented as
a rectangle with a label inside that accepts mouse events. Adding
more lightweight buttons means drawing more rectangles, which entails
very little additional overhead.
A lightweight component needs to be drawn on something, and an application
written in the Java programming language needs to interact with the local window
manager so that the main application window can be closed or minimized. This
is why the top-level parent components mentioned above (JFrame
,
JApplet
, and others) are implemented as heavyweight
components—they need to be mapped to a component in the local window
toolkit.
Change the FileIO
code so error messages such as
Cannot read from text.txt
are properly removed when
the user selects a new menu item. You might recall from the
Painter Class Description section that this
error message remained in the message area after the user selected
Save text to file
and the text was successfully saved.
The example application showed you how to get started using menus, editable text areas, layout managers, and styled text. To continue your exploration and learn more about Project Swing, Java 2D graphics, or the AWT, you might want to look at some of the following resources:
© 1994-2005 Sun Microsystems, Inc.