Menus, Layouts, Styled Text, and Simple Geometry

   
   

Articles Index | GUI Articles Index | New To Java Center

2D Graphics
Menus, Layouts, Styled Text, and Simple Geometry

 

Book Jacket

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);

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:

  1. 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.
  2. 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.

Book Jacket
JAppletJDialogJFrameJWindowjava.awt.Buttonjava.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.

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.