|
Articles Index |
GUI Articles Index |
New To Java Center
2D Graphics
Menus, Layouts, Styled Text, and Simple Geometry
By Monica Pawlan
January 5, 2001
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.
About the Example
The application is a very scaled down text editor. Its user interface consists
of a 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.
Run the Example
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
FileIO Class Description
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.
Creating the File Menu
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);
Dividing the UI into Panels
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
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.
Using Styled Text for Notification Messages
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);
|
Adding the Editable Text Area
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);
|
Listening for Events
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
|
Acting on Events
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.
-
When Clear Display is selected, the
clearDisplay
method is called to clear error message, notification message,
or editable text from the display.
-
When Save Text to File is selected, text in the editable text
area is retrieved and saved to the file. A notification message
lets the user know the operation succeeded. If no text is found
in the editable text area or if any other error is encountered,
an error message appears.
-
When Get Text from File is selected, text saved to the file
is loaded into the editable text area. A notification message
lets the user know the operation succeeded. If an error is encountered,
an error message appears.
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");
}
}
}
}
}
|
System Properties
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.
File.separatorChar
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.
Painter Class Description
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:
-
The user selected Get Text from File but the file did not
exist, so the Cannot read from text.txt error message
appeared in the south area of Panel 1.
-
The user typed text into the editable text area and selected
Save Text to File. A notification messages appears in the
center area of Panel 1 and the error message remains in the south
area. Normally, you would want to erase the error message from
the previous failed operation, but leaving it there helps illustrate
where the drawing area defined by the
panel1paint object
begins and ends.
Lightweight and Heavyweight Components
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
componentsthey need to be mapped to a component in the local window
toolkit.
Exercise
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.
More Information
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:
Monica Pawlan is a manager and writer at Sun Microsystems, Inc., who enjoys learning and writing about new Java platform technologies. She also likes to garden, play guitar, and travel.
Have a question about programming?
Use Java Online Support.
|