Module 9B: Swing, Part I


Introduction

While AWT is powerful enough to create many sophisticated applications, and to develop custom components, it is cumbersome.

Java has now provided the "Swing" library as a standard part of the JDK.

Swing differs from AWT:

What can't you do with Swing?

The really quick introduction to Swing, for an AWT programmer:

Typical classes you can expect to use include:


Bringing up a window

Let us create a simple blank window that does nothing:

Next, let's create a slightly improved window: (source file)

import java.awt.*;
import javax.swing.*;

public class TestSwing2 {

  public static void main (String[] argv)
  {
    // Create a Frame instance. 
    JFrame f = new JFrame ();

    // Set the size. 
    f.setSize (200, 100);

    // Install a title for the titlebar. 
    f.setTitle ("A Test Window");

    // A background color: 
    //   First, extract the contentPane 
    Container cPane = f.getContentPane();
    //   Then, set its color. 
    cPane.setBackground (Color.cyan);

    // Display. 
    f.setVisible (true);
  }

}
  
The result:
Window

Note:

In-Class Exercise 9.1: Use the above code in the following template to print out the screen size in inches. Next, bring up a window that is 3 inches wide and 2 inches in height and place the window approximately in the center of the screen. (Use the setLocation method of Component).


Simple drawing and writing

We will now look some basic graphics (drawing and lettering):

Consider the appropriate widget for drawing:

  • An AWT programmer would try to "paint" on a JFrame.
    Important: Don't override paint() unless you know what you are doing!

  • Instead, in Swing, we usually draw on a JPanel.

  • Turns out, you can draw on most Swing components, but are not advised to draw on top-level components like JFrame.

All drawing occurs on a graphics context:

  • A graphics context is an instance of class Graphics.

  • However, you cannot instantiate this class yourself.

  • A component (like JPanel) does that for you.

  • Thus, to draw on a JPanel, you need to get a Graphics instance from the panel.

  • The way this works is, you get the Graphics instance as a parameter of paintComponent (Graphics g).


The all-important technique of overriding paintComponent() (and, sometimes, repaint()):

  • Like AWT, Swing does not maintain "bit-mapped memory".

  • That is, if a window is covered and later uncovered, Swing does not re-draw the contents.

  • Thus, when re-drawing is needed, you have to do it.

  • Swing tells you when drawing is needed by calling the paintComponent() method.

  • Thus, you need to override the paintComponent() method to "do your thing".

  • Important: Therefore, you need to extend the appropriate class (usually JPanel to override the paintComponent() method.
  • This method is given a Graphics instance as parameter.

Let's look at a simple example: (source file)

import java.awt.*;
import javax.swing.*;

// Extend JPanel to override its paintComponent() method: 

class NewPanel extends JPanel {

  // Set background in constructor. 

  public NewPanel () 
  {
    this.setBackground (Color.cyan);
  }
  

  // Override paintComponent(): 

  public void paintComponent (Graphics g)
  {
    // Always call super.paintComponent (g): 
    super.paintComponent(g);

    // drawString() is a Graphics method. 
    // Draw the string "Hello World" at location 100,100 
    g.drawString ("Hello World!", 100, 100);

    // Let's find out when paintComponent() is called. 
    System.out.println ("Inside paintComponent");
  }

}

public class TestSwing4 {

  public static void main (String[] argv)
  {
    // Create a frame 
    JFrame f = new JFrame ();

    // Set the title and other parameters. 
    f.setTitle ("Hello World Test");
    f.setResizable (true);
    f.setSize (500, 300);

    // Add the panel using the default BorderLayout 
    NewPanel panel = new NewPanel ();
    Container cPane = f.getContentPane ();
    cPane.add (panel);

    // Show the frame. 
    f.setVisible (true);
  }

}
  
Here is the result:
Window

An explanation:

  • To write or draw in a frame, we need to override the paintComponent() method in JPanel.

  • To override means we have to inherit from JPanel.

  • Above, the class NewPanel extends JPanel:
    class NewPanel extends JPanel {
    
      // ... 
    
    }
        

  • Since the NewPanel component will fill up the contentPane, it's background will be visible.
    Therefore, we set the Panel's background color to what we want.

  • Note that paintComponent() is passed a Graphics context.

  • Only a graphics context has the means for drawing and writing.

  • Here are some methods in Graphics, for example:
    • For writing: drawString(), setFont().
    • For drawing empty shapes: drawRect(), drawOval, drawPolygon(), drawPolyline(), drawArc().
    • For drawing filled shapes: fillRect(), fillOval(), fillPolygon(), fillArc().
    • For drawing a line: drawLine()
    • For clipping and translating: setClip(), translate().

  • All measurements are in pixels:
    • The frame size is 500 x 300.
    • The drawString method draws the specified string at a location specified from the top-left corner.
      (The origin is at the topleft corner).

Next, let us draw some geometric figures:

  • We will draw un-filled and filled versions of a square, a circle and a rectangle.

  • All the drawing methods are in Graphics.

  • We will use methods drawRect(), drawOval() and drawRoundRect().

  • drawRect (int topleftx, int toplefty, int width, int height):
    • The first two integers specify the topleft corner.
    • The next two are the desired width and height of the rectangle.


  • drawOval (int topleftx, int toplefty, int width, int height):
    • The first two integers specify the topleft corner.
    • The next two are the desired width and height of the enclosing rectangle.


  • drawRect (int topleftx, int toplefty, int width, int height, int arc_width, int arc_height):
    • The first four numbers specify the rectangle.
    • The last two specify "roundedness".
    • The larger the values, the more rounded the result.
    • How the corners are constructed:
      • An imaginary oval is constructed in a rectangle of dimensions arc_width and arc_height.
      • The oval is broken into four quadrants.
      • Each arc is used as a corner of the target rectangle.


  • The drawLine() method is used to draw lines:
    • Unfortunately, the line thickness is fixed at one pixel.
    • To draw thicker lines, you have to "pack" one-pixel lines together yourself.
      (If the lines are axis-parallel, you can use filled rectangles).

For example, consider the following code: (source file)

import java.awt.*;
import javax.swing.*;

// Extend JPanel to override paintComponent () 
class NewPanel extends JPanel {

  public NewPanel () 
  {
    this.setBackground (Color.cyan);
  }

  public void paintComponent (Graphics g)
  {
    super.paintComponent (g);
    
    // Draw a Square: 
    g.drawRect (50,50,50,50);
    g.drawString ("Square", 50, 115);

    // Circle: 
    g.drawOval (200,50,50,50);
    g.drawString ("Circle", 200, 115);

    // Rounded rectangle: 
    g.drawRoundRect (350,50,75,50,20,20);
    g.drawString ("Rectangle", 350, 115);

    // Draw a line across the middle: 
    g.drawLine (0,150,500,150);

    // Now draw some filled shapes: 

    // Square: 
    g.fillRect (50,200,50,50);
    g.drawString ("Square", 50, 265);

    // Circle: 
    g.fillOval (200,200,50,50);
    g.drawString ("Circle", 200, 265);

    // Rounded rectangle: 
    g.fillRoundRect (350,200,75,50,20,20);
    g.drawString ("Rectangle", 350, 265);
  }

}

public class TestSwing5 {

  public static void main (String[] argv)
  {
    // Create an instance of NewFrame 
    JFrame f = new JFrame ();

    // Set the title and other parameters. 
    f.setTitle ("Some Geometric Figures");
    f.setResizable (true);
    f.setSize (500, 300);

    // Create and add the panel. 
    NewPanel panel = new NewPanel ();
    // Note alternative syntax: 
    f.getContentPane().add (panel);

    // Show the frame. 
    f.setVisible (true);
  }

}
  
The result produced is:
Window

We can improve the code slightly by placing all the frame-related code inside NewFrame itself: (source file)

import java.awt.*;
import javax.swing.*;

// Extend JPanel to override paintComponent () 
class NewPanel extends JPanel {

  public NewPanel () 
  {
    this.setBackground (Color.cyan);
  }

  public void paintComponent (Graphics g)
  {
    super.paintComponent (g);
    
    // Draw a Square: 
    g.drawRect (50,50,50,50);
    g.drawString ("Square", 50, 115);

    // Circle: 
    g.drawOval (200,50,50,50);
    g.drawString ("Circle", 200, 115);

    // Rounded rectangle: 
    g.drawRoundRect (350,50,75,50,20,20);
    g.drawString ("Rectangle", 350, 115);

    // Draw a line across the middle: 
    g.drawLine (0,150,500,150);

    // Now draw some filled shapes: 

    // Square: 
    g.fillRect (50,200,50,50);
    g.drawString ("Square", 50, 265);

    // Circle: 
    g.fillOval (200,200,50,50);
    g.drawString ("Circle", 200, 265);

    // Rounded rectangle: 
    g.fillRoundRect (350,200,75,50,20,20);
    g.drawString ("Rectangle", 350, 265);
  }

}


// Create a new Frame class. 

class NewFrame extends JFrame {

  public NewFrame ()
  {
    // Set the title and other parameters. 
    this.setTitle ("Some Geometric Figures");
    this.setResizable (true);
    this.setSize (500, 300);

    // Create and add the panel. 
    NewPanel panel = new NewPanel ();
    this.getContentPane().add (panel);

    // Show the frame. 
    this.setVisible (true);
  }
  
}

public class TestSwing6 {

  public static void main (String[] argv)
  {
    // Simply fire up the whole application. 
    NewFrame f = new NewFrame ();
  }

}
  
Note:
  • We used a constructor to set JFrame attributes.

  • Since methods in JFrame need to be called for the particular instance, we use this for clarity.

  • We could use a no-parameter constructor to create a default size.

  • Note the unusual syntax in replacing
        Container cPane = f.getContentPane ();
        cPane.add (panel);
       
    with the variable-free
        f.getContentPane().add (panel);
       
    If you are uncomfortable with this form, use the former syntax.

In-Class Exercise 9.2: Use the following template to draw a standard 8x8 chessboard. Let the size of the chessboard be determined by minimum of the two dimensions of the panel. Place the topleft corner of the chessboard at the topleft corner of the panel.


Frame insets

In the above chessboard exercise you may have noticed two things:

  • Since the panel is completely "inset" into the JFrame contentpane, there is no "inset" problem as there was in AWT.

  • A filled rectangle is slightly smaller than an empty one of the same size (one pixel smaller on two sides).
To fix the latter problem, you can draw a line around the periphery of the chessboard.

Note:

  • The graphics instance will let you draw well outside the actual visible area.
    However, only the visible area will be shown.

  • For example, the following code for a 500x300 frame draws well outside the visible region: (source file)
      public void paintComponent (Graphics g)
      {
        super.paintComponent (g);
    
        // Draw a wide rectangle: 
        g.fillRect (-100, 100, 800, 50);
      }
       
  • One additional shortcut:
        // Create and add the panel. 
        this.getContentPane().add (new NewPanel());
       

In-Class Exercise 9.3: Modify your Ex 9.3 code or use this template to dynamically compute the size of the Panel in paintComponent(). You can do this by getting the width and height by calling the getWidth() and getHeight() methods of the Panel itself. This way, if the window is re-sized, the chessboard will fit the new window.


Writing with fonts

Swing's support for text is better than AWT's, but still limited:

  • Additional font families are supported.

  • You can load your own font families.

  • Some text layout help is provided.

The following code tells you what fonts are supported:

    // Print out available font names. 
    GraphicsEnvironment gEnv = GraphicsEnvironment.getLocalGraphicsEnvironment();
    String[] fNames = gEnv.getAvailableFontFamilyNames();
    System.out.println ("Available font families: ");
    for (int i=0; i < fNames.length; i++)
      System.out.println ("  " + fNames[i]);
  
Note:
  • GraphicsEnvironment is a general-purpose AWT class that bundles together many system-related properties.

  • To get an instance of GraphicsEnvironment, you need to call its static getLocalGraphicsEnvironment() method.

  • The instance method getAvailableFontFamilyNames() provides a list of fonts, returned as a String-array.

  • Examples of what was printed out on Linux (Redhat 6.0):
      Bitstream Charter
      BookmanL
      CenturySchoolbookL
      ChanceryL
      Courier
      Courier 10 Pitch
      Default
      Dialog
      DialogInput
      Dingbats
      GothicL
      Lucida Bright
      Lucida Sans
      Lucida Sans Typewriter
      Monospaced
      NimbusMonoL
      NimbusRomanNo9L
      NimbusSansL
      PalladioL
      SansSerif
      Serif
      StandardSymbolsL
      Utopia
      Zapf Dingbats
      

About fonts:

  • AWT's (and Swing's) font functionality is found in the classes Font and FontMetrics.

  • A font is specified by a type (or name), a size and a style.

  • Font name: the name of the Font family, e.g., Zapf Dingbats

  • Font styles:
    • A font style is the particular thickness and slant used for emphasis.
    • The constants Font.PLAIN, Font.ITALIC and Font.BOLD are used to specify styles.
    • AWT supports 4 styles:
      • Plain:
        • This is the regular appearance of the font.
        • To specify, use Font.PLAIN.
      • Italic:
        • This is a slanted version for emphasis.
        • To specify, use Font.ITALIC.
      • Bold-plain:
        • This is a boldface version of the plain style.
        • To specify, use Font.PLAIN | Font.BOLD.
          (Logical OR).
      • Bold-italic:
        • This is a boldface version of the italic style.
        • To specify, use Font.ITALIC | Font.BOLD.


  • Font sizes:
    • A font's size is specified using the standard "printer's point" measure: one "point" is about 1/72 inches.
    • AWT does not guarantee that your size specification will be implemented exactly - only a "best attempt" is made.
    • The FontMetrics class provides more information about a font's sizes.
    • Note: a font size is not specified in pixels.


  • To use a font, you need to:
    • Create an instance of Font, passing the name, style and size to the constructor.
    • To specify a style, use the constants in class Font.
    • Pass the Font instance to the Graphics context using the setFont() method.

Consider this example, which prints out Serif and SansSerif strings in two sizes and all the available styles: (source file)

import java.awt.*;
import javax.swing.*;

// Extend JPanel to override paintComponent () 
class NewPanel extends JPanel {

  public NewPanel () 
  {
    this.setBackground (Color.cyan);
  }

  public void paintComponent (Graphics g)
  {
    super.paintComponent (g);

    // Create 10-point Serif fonts in all styles: 
    Font f = new Font ("Serif", Font.PLAIN, 10);
    g.setFont (f);
    g.drawString ("Serif-Plain-10pt", 30, 60);

    f = new Font ("Serif", Font.ITALIC, 10);
    g.setFont (f);
    g.drawString ("Serif-Italic-10pt", 30, 90);

    f = new Font ("Serif", Font.PLAIN | Font.BOLD, 10);    
    g.setFont (f);
    g.drawString ("Serif-Plain-Bold-10pt", 30, 120);

    f = new Font ("Serif", Font.ITALIC | Font.BOLD, 10);    
    g.setFont (f);
    g.drawString ("Serif-Italic-Bold-10pt", 30, 150);

    // Create 20-point SansSerif fonts in all styles: 
    f = new Font ("SansSerif", Font.PLAIN, 20);
    g.setFont (f);
    g.drawString ("SansSerif-Plain-20pt", 160, 60);

    f = new Font ("SansSerif", Font.ITALIC, 20);
    g.setFont (f);
    g.drawString ("SansSerif-Italic-20pt", 160, 90);

    f = new Font ("SansSerif", Font.PLAIN | Font.BOLD, 20);    
    g.setFont (f);
    g.drawString ("SansSerif-Plain-Bold-20pt", 160, 120 );

    f = new Font ("SansSerif", Font.ITALIC | Font.BOLD, 20);    
    g.setFont (f);
    g.drawString ("SansSerif-Italic-Bold-20pt", 160, 150);
  }

} // End of NewPanel 



// Create a new Frame class. 

class NewFrame extends JFrame {

  public NewFrame ()
  {
    // Set the title and other parameters. 
    this.setTitle ("Some Geometric Figures");
    this.setResizable (true);
    this.setSize (500, 250);

    // Create and add the panel. 
    NewPanel panel = new NewPanel ();
    this.getContentPane().add (panel);

    // Show the frame. 
    this.setVisible (true);

    // Print out available font names. 
    GraphicsEnvironment gEnv = GraphicsEnvironment.getLocalGraphicsEnvironment();
    String[] fNames = gEnv.getAvailableFontFamilyNames();
    System.out.println ("Available font families: ");
    for (int i=0; i < fNames.length; i++)
      System.out.println ("  " + fNames[i]);
  }
  
}

public class TestSwing11 {

  public static void main (String[] argv)
  {
    NewFrame nf = new NewFrame ();
  }

}
  
The result produced is:
Window

Note:

  • The font name is passed as a string:
        Font f1 = new Font ("Serif", Font.PLAIN, 10);
        
  • The font style is an OR'ed combination of constants, e.g.,
        f1 = new Font ("SansSerif", Font.ITALIC | Font.BOLD, 20);    
        
  • To use a font, you first "set" the font using the setFont() method in the graphics context.

  • The drawString method of Graphics will then draw the specified fonts at a given location.
        g.drawString ("Hello World!", 30, 60);
        
  • The lowest point of a non-descending letter like "h" is used for the y-value.

  • You, the programmer, are responsible for laying out the strings (typesetting):
    - you need to compute line lengths, inter-word spacing, etc.

Typesetting:

  • A font has several measurements (characteristics) useful in typesetting:
    • Baseline: An imaginary line on which the characters in a line are placed.
      - usually, the lowest point of a non-descending character like "h".
    • Ascent: the height of a non-descending character above the baseline.
    • Descent: the distance a descender like "y" dips below the baseline.
    • Leading (pronounced "ledding"): the distance between the descent of one line and the ascent of the next line.
      (Space between lines, essentially).
    • Height: The sum of the ascent, descent and leading.
  • Note: the font characteristics depend on the letters in the text.
    • An accent is not counted in the ascent.


  • A FontMetrics instance provides the characteristics for a particular font, style and size.

  • A FontMetrics instance is usually obtained as follows:
        // Create a font.  
        Font f = new Font ("Serif", Font.PLAIN | Font.BOLD, fontsize);    
    
        // Set the font in the graphics instance.  
        g.setFont (f);
    
        // Get a FontMetrics instance from the graphics instance.  
        FontMetrics fm = g.getFontMetrics();
        
  • The class FontMetrics in java.awt has several methods:
      public abstract FontMetrics implements Serializable {
    
        //...  
    
        // Some methods that return a font characteristic.  
        public int getAscent ();
        public int getDescent ();
        public int getLeading ();
        public int getHeight ();
        public int maxAscent ();
        public int maxDescent ();
    
        // Get the resulting width of a string.  
        public int stringWidth (String s);
    
        //...  
    
      }
       
  • If accents are being used, it may be advisable to use the sum of maxAscent, maxDescent and leading.

We will consider an example in which:

  • We are given a piece of text as an array of String's, and we want to write out the words so that words are not broken up.

  • We will allow the font size to be specified.

  • We will use the stringWidth() method in FontMetrics to get the size of a string in pixels.
Here is code: (source file)
import java.awt.*;
import javax.swing.*;

// Extend JPanel to override paintComponent () 
class NewPanel extends JPanel {

  String[] words; // List of words to display. 
  int fontsize;   // Size of font. 

  public NewPanel (String[] words, int fontsize)
  {
    // Retain data and font size. 
    this.words = words;
    this.fontsize = fontsize;

    this.setBackground (Color.cyan);
  }

  public void paintComponent (Graphics g)
  {
    super.paintComponent (g);
    // Create a Serif font in the given font size. 
    Font f = new Font ("Serif", Font.PLAIN | Font.BOLD, fontsize);    
    g.setFont (f);

    // Get a FontMetrics instance from the graphics context. 
    FontMetrics fm = g.getFontMetrics();

    // Get the height (height of font plus separator). 
    int fontheight = fm.getHeight ();

    // Size of frame plus insets. 
    Dimension D = this.getSize ();

    // Visible drawing area 
    int max_width = D.width;
    int max_height = D.height;

    // Start at topleft. 
    int startx = 0;

    // Must add font height since bottom left corner  
    // of font is used as starting point. 
    int y = 0 + fontheight;
    
    int x = startx;
    for (int i=0; i < words.length; i++) {

      // Obtain pixel-length of string (with blank) 
      int len = fm.stringWidth (" " + words[i]);

      if (x + len < max_width) {
        // If it fits within the current line, draw it 
	g.drawString (" " + words[i], x, y);
	x += len;
      }
      else {
	// Go to next line. 
	x = startx;
	y += fontheight;
	g.drawString (" " + words[i], x, y);
	x += len;
      }
    }

  }
  
} // End of NewPanel 

class NewFrame extends JFrame {

  // Constructor. 
  public NewFrame (int width, int height, String[] words, int fontsize)
  {
    // Set the title and other frame parameters. 
    this.setTitle ("Font metrics");
    this.setResizable (true);
    this.setSize (width, height);

    // Create and add the panel. 
    NewPanel panel = new NewPanel (words, fontsize);
    this.getContentPane().add (panel);

    // Show the frame. 
    this.setVisible (true);
  }

} // End of class "NewFrame" 

public class TestSwing12 {

  public static void main (String[] argv)
  {
    String[] testwords = {
      "There", "once", "was", "a", "poet", "named", "Heinz",
      "All", "of", "whose", "poems", "had", "just", "two", "lines."
    };
    NewFrame nf = new NewFrame (750, 200, testwords, 35);
  }

}
  

This prints out the following:
Window
Note:

  • We have used the method getSize() in Component (and therefore in NewFrame) to get the window size:
    (The window size may be changed.)
        Dimension D = this.getSize ();
        
  • The x-value is the leftmost drawable location:
        int startx = 0;
        
  • However, the first baseline is well-below the the top of the drawable area.

  • Here, we add the font's height (adding the ascent is actually enough):
        int y = 0 + fontheight;
        
  • The list of words and desired font size is passed to the constructor:
        NewFrame nf = new NewFrame (750, 200, testwords, 35);
        

In-Class Exercise 9.4: Use the following template to create a frame in which you write the largest possible "Hello World!" that will fit into the drawable area. Start with a font size of 10 and increase the size in increments of 10 until you find the largest possible font that will fit into the drawable area. Allow for the situation where the window may be resized.


Images

Java provides some support for loading, manipulating and displaying images in a window:

  • Simple features include:
    • Loading a JPEG, GIF or animated GIF image from a file or URL.
    • Displaying the image in a Frame using the drawImage method in Graphics.
    • Tracking the "wait" during image upload.


  • Advanced features include:
    • Creating a buffer for images.
    • Manipulating images: shrinking, rotating.
    • Defining and using image filters.


  • The classes for most simple features are in java.awt whereas the more complicated stuff is in java.awt.image.

In this module, we will only consider simple image features.

About image formats

  • Images consist of pixel values: each pixel is determined by its "RGB" values, typically 8-bits for each value.

  • Images are typically stored in compressed format.

  • GIF (Graphical Interchange Format) is one such format:
    • GIF (created by CompuServe) uses the lossless Lempel-Ziv-Welch (LZW) algorithm.
    • GIF is probably the most popular format.
    • However, there is a patent controversy surrounding GIF.
      (Unisys holds the LZW patent).


  • JPEG (Joint Photographic Experts Group) is another format:
    • JPEG uses lossy compression based on a number of image-processing techniques such as transforms (DCT) and quantization.
    • JPEG is free.
    • JPEG is really an algorithm spec rather than a file spec.


  • PNG (Portable Network Graphics) format:
    • Lossless format, like GIF.
    • Slightly better compression than GIF.
    • Free of patent issues.
    • Support for transparency and progressive display.


  • Java handles GIF, JPEG and PNG formats and decides based on the extensions .gif, .jpeg or .jpg, and .png.

The following classes will be useful:

  • java.awt.Image:
    • An Image instance is used to store and maintain pixels from an image.
    • The full set of pixels is not necessary -- any subset will do.
    • Image does not provide access to the raw pixels.
    • To actually get at the pixels, you need to use PixelGrabber.


  • java.awt.Toolkit:
    • Toolkit is a motley collection of useful methods bundled together into a single class, in package java.awt.
    • You cannot create an instance, but must instead use the method Toolkit.getDefaultToolkit().
    • The getImage() method is the most important method of the Toolkit instance.
    • getImage() comes in two flavors:
         public Image getImage (String filename);
         public Image getImage (URL u);
            
    • Both methods return an Image instance.


  • MediaTracker:
    • The getImage() method above is written to return immediately.
    • Thus, as the image loads, it starts filling up the window, typically, top-down.
    • If you want to wait until the entire image has loaded, use a MediaTracker instance.


  • Graphics:
    • As with any rendering, the Graphics instance passed to the paintComponent() method is used.
    • The method drawImage() (and its variants) is is used to display an image.


  • ImageObserver:
    • ImageObserver is really an interface in java.awt.
    • It has just one method.
    • The method is used by a image-rendering process to report on progress, on errors etc.
    • An instance must be passed to drawImage().
    • The class Component (and therefore JPanel) implements this interface.
    • Thus, the JPanel instance itself is usually passed via this.

Let's look at a simple example: (source file)

import java.awt.*;
import javax.swing.*;

// Extend JPanel to override paintComponent () 
class NewPanel extends JPanel {

  public NewPanel () 
  {
    this.setBackground (Color.cyan);
  }

  public void paintComponent (Graphics g)
  {
    super.paintComponent (g);

    // Get a Toolkit instance. 
    Toolkit tk = Toolkit.getDefaultToolkit ();

    // Provide a filename to the getImage() method. 
    Image img = tk.getImage ("bugs.gif");

    // Use the drawImage method. 
    g.drawImage (img, 0, 0, this);
    // Note: we pass "this" as a parameter. 
  }
  
} // End of NewPanel 

class NewFrame extends JFrame {

  // Constructor. 
  public NewFrame (int width, int height)
  {
    // Set the title and other frame parameters. 
    this.setTitle ("Bugs");
    this.setResizable (true);
    this.setSize (width, height);

    // Create and add the panel. 
    NewPanel panel = new NewPanel ();
    this.getContentPane().add (panel);

    // Show the frame. 
    this.setVisible (true);
  }



} // End of class "NewFrame" 

public class TestSwing13 {

  public static void main (String[] argv)
  {
    NewFrame nf = new NewFrame (300, 200);
  }

}
  

The result produced is:
Window

Note:

  • The format is GIF.

  • The file is assumed to be in the same directory.

  • Since the insets were not used, part of the image gets clipped.

  • The definition of drawImage() used is:
      public void boolean drawImage (Image img, int x, int y, ImageObserver I);
        
    Here:
    • You pass an Image instance (img).
    • You pass the point where the topleft corner of the image is to be located.
    • You pass an ImageObserver instance.
    • Note that Component implements the ImageObserver interface:
        public abstract class Component 
          implements ImageObserver, MenuContainer, Serializable {
      
          // ...  
      
        }
            
    • Thus, Frame, which is a subclass of Component, also implements ImageObserver.
    • Therefore, we can pass the current Frame instance (via this) as the fourth parameter:
          g.drawImage (img, 0, 0, this);
            
    • You don't really need to understand the purpose of ImageObserver at this time.

    Finally, we can clean up the code in NewPanel a little:

    class NewPanel extends JPanel {
    
      Image img;
    
      public NewPanel () 
      {
        this.setBackground (Color.cyan);
        // Get a Toolkit instance. 
        Toolkit tk = Toolkit.getDefaultToolkit ();
    
        // Provide a filename to the getImage() method. 
        img = tk.getImage ("bugs.gif");
    
      }
    
      public void paintComponent (Graphics g)
      {
        super.paintComponent (g);
    
        g.drawImage (img, 0, 0, this);
      }
      
    } // End of NewPanel 
      

In the next example, we will fetch an image from a URL: (source file)

import java.awt.*;
import javax.swing.*;
import java.net.*;  // For class URL. 

// Extend JPanel to override paintComponent () 
class NewPanel extends JPanel {

  public NewPanel () 
  {
    this.setBackground (Color.cyan);
  }

  public void paintComponent (Graphics g)
  {
    super.paintComponent (g);

    Toolkit tk = Toolkit.getDefaultToolkit ();

    // try-catch needed since URL constructor throws an exception. 
    try {
      // URL is in java.net. 
      URL u = new URL ("http://www.seas.gwu.edu/~drum/canyon.jpg");

      // Load the image using the second form of getImage(). 
      Image img = tk.getImage (u);

      // Display image. 
      g.drawImage (img, 0, 0, this);
    }
    catch (MalformedURLException e) {
      System.out.println (e);
    }

  }
  
} // End of NewPanel 
  

class NewFrame extends JFrame {

  // Constructor. 
  public NewFrame (int width, int height)
  {
    // Set the title and other frame parameters. 
    this.setTitle ("Gif image");
    this.setResizable (true);
    this.setSize (width, height);

    // Create and add the panel. 
    NewPanel panel = new NewPanel ();
    this.getContentPane().add (panel);

    // Show the frame. 
    this.setVisible (true);
  }

} // End of class "NewFrame" 

public class TestSwing14 {

  public static void main (String[] argv)
  {
    NewFrame nf = new NewFrame (600, 300);
  }

}
  
The result is:
Window

Note:

  • The class URL is in package java.net.

  • An actual URL is passed to the constructor of the class URL:
          URL u = new URL ("http://www.seas.gwu.edu/~drum/canyon.jpg"); 
        
  • The getImage() method of Toolkit has a form that takes in a URL instance.

  • If you try it out, you will notice that it takes time for the image to load.

  • The image is loaded slowly (top-down).

In-Class Exercise 9.5: Use the following template to create a frame in which you load the image at URL http://www.seas.gwu.edu/~drum/mystery_person.jpg. and then fit the image so that it fits exactly into the drawable area. To do this:

  • use the method getSize() in JPanel to get a Dimension instance which contains the width and height of the frame.
  • use another version of drawImage() that allows you to specify the desired width and height for the image:
      public boolean drawImage (Image img, int x, int y, int width, 
                                int height, ImageObserver I);
       
Can you identify the person in the picture?

Next, we will use a MediaTracker instance to complete loading before display: (source file)

  public void paintComponent (Graphics g)
  {
    super.paintComponent (g);

    Toolkit tk = Toolkit.getDefaultToolkit ();

    try {
      URL u = new URL ("http://www.seas.gwu.edu/~drum/canyon.jpg"); 
      Image img = tk.getImage (u);

      // Create an instance of MediaTracker and  
      // pass a Frame instance as parameter (via "this"). 
      MediaTracker mt = new MediaTracker (this);

      // Add the image instance to the tracker. 
      mt.addImage (img, 1);

      // We will use a "wait" (clock) cursor while the 
      // image is loading. 
      this.setCursor (Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));

      // Ask the MediaTracker to wait. 
      try {
	mt.waitForID (1);
      }
      catch (InterruptedException e) {
	System.out.println (e);
      }
      // Now the image has been loaded. 

      // Return to default cursor. 
      this.setCursor (Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));

      // Draw the complete image. 
      g.drawImage (img, 0, 0, this);
    }
    catch (MalformedURLException e) {
      System.out.println (e);
    }
  }
  

In-Class Exercise 9.6: Use the following template to create a frame in which you load the four images guy1.gif, guy2.gif, guy3.gif and guy4.gif at URL http://www.seas.gwu.edu/~drum/. (Thus, the URL for guy1.gif is http://www.seas.gwu.edu/~drum/guy1.gif). This time, instead of fitting the images to the frame, fit the frame to hold all images in a row so that the frame width at least the sum of the image widths and the frame height is at least as large the the maximum height of the images. The following methods will be useful:

  • Use MediaTracker to wait for all images to be loaded.
  • Class Image has methods getWidth() and getHeight() to obtain the width and height of an image (after its loaded).
  • Both the above methods need to be given an ImageObserver instance as a parameter.
  • Use setSize(int w, int h) to set the size of the frame.
  • Finally, use drawImage() to draw the images at the right places.


Interaction: buttons

Thus far, we have only drawn or painted on a window.

To allow a user to interact, we need to receive user input in the form of mouse-clicks or keyboard characaters.

We will first consider a simple "quit" button as an example: (source file)

import java.awt.*;
import javax.swing.*;

class NewFrame extends JFrame {

  // Constructor. 
  public NewFrame (int width, int height)
  {
    // Set the title and other frame parameters. 
    this.setTitle ("Button example");
    this.setResizable (true);
    this.setSize (width, height);

    // Extract the contentPane because we'll  
    // need to refer to it a couple of times. 
    Container cPane = this.getContentPane();
    cPane.setBackground (Color.cyan);

    // Create a Button instance and pass the button  
    // label as a parameter to the constructor. 
    JButton b = new JButton ("Quit");

    // Set the layout manager for our Frame. 
    cPane.setLayout (new BorderLayout());

    // Add the button to the frame. 
    cPane.add (b);

    // Show the frame. 
    this.setVisible (true);
  }

} // End of class "NewFrame" 


public class TestSwing16 {

  public static void main (String[] argv)
  {
    NewFrame nf = new NewFrame (300, 200);
  }

}
  

The result produced is:
Window

Note:

  • The button occupies the entire visible area of the frame.
    (This is a result of the layout method used).

  • The default button color is grey.

  • This button does flash when clicked, but nothing happens.
    (We haven't created a button-pressed event-handler yet).

  • Class javax.swing.JButton is used to create a button instance:
    • Here is (part of) the definition of JButton:
        public class JButton extends AbstractButton implements Accessible {
      
          // Constructors.  
          public JButton ();
          public Button (String label);
          public Button (Icon I);
          public Button (String label, Icon I);
      
          // Some important methods inherited from AbstractButton: 
          public void addActionListener (ActionListener a);
          public String getText ();
          public synchronized void setText (String label);
      
          // ...  
        }
            
    • The text inside a button is passed as a String to the JButton constructor:
           JButton b = new JButton ("Quit");
         
    • Since JButton extends JComponent (and Component), it inherits a whole bunch of methods, e.g.,
      • setBackground (Color c) - set the background color of the button.
      • setForeground (Color c) - set the font color.
      • setFont (Font) - set the font used.
      • setText (String) - set label.


  • Each container uses a layout manager to arrange components for display:
    • Layout managers don't let you control exact locations, but give you some options in controlling the layout.
    • Layout managers also ignore sizes of components
    • These are examples of layout managers:
      1. BorderLayout: this manager tries to place components either in one of five locations: North, South, East, West or Center (default).
      2. FlowLayout: this manager places components left to right and row-by-row (in standard scan order).
      3. CardLayout: this manager displays only one component at a time, like a rolodex.
      4. GridLayout: this manager places components in a grid.
      5. GridBagLayout: this manager uses a grid-like approach that allows for different row and column sizes.
    • Each layout manager is a class in java.awt or javax.swing.
    • We will cover layout managers later.


  • The setLayout() method is used to choose the layout manager.

  • The default layout manager for a Frame is BorderLayout, so the setLayout() call was not really necessary, but was used here to highlight the layout manager.

  • The add method is used to add a component:
    • The add method above has this signature:
          public Component add (Component c);
            
    • Since JButton derives from Component, it can be passed as a parameter.
    • Note: we did not use the return value (it has an esoteric purpose).


  • Finally, the code for creating and adding a button is inside the constructor of NewFrame:
    • Note that placing the button creation code in paintComponent() won't work: (source file)
        public void paintComponent (Graphics g)
        {
          super.paintComponent (g);
      
          JButton b = new JButton ("QUIT");
      
          // Add the button to the panel 
          this.add (b);
          this.validate ();
        }
            
    • An alternative (also not desirable) is to place the button outside the frame, e.g., (source file)
      public class TestSwing18 {
      
        public static void main (String[] argv)
        {
          NewFrame nf = new NewFrame (300, 200);
          // Extract the contentPane because we'll  
          // need to refer to it a couple of times. 
          Container cPane = nf.getContentPane();
          cPane.setBackground (Color.cyan);
      
          // Create a Button instance and pass the button  
          // label as a parameter to the constructor. 
          JButton b = new JButton ("Quit");
          b.setBackground (Color.cyan);
      
          // Set the layout manager for our Frame. 
          cPane.setLayout (new BorderLayout());
      
          // Add the button to the frame. 
          cPane.add (b);
      
          nf.validate();
        }
      
      }
            
    • Note that we used the validate() method of Container (inherited by NewFrame that is needed to "reset" the interior, so that the button can be displayed.

Next, let us try making small changes in the layout:

  1. First, we will explicitly ask BorderLayout to place the button in the "South" area of the window: (source file)
        // Create a Button instance and pass the button  
        // label as a parameter to the constructor. 
        JButton b = new JButton ("Quit");
        b.setBackground (Color.cyan);
    
        // Set the layout manager for our Frame. 
        cPane.setLayout (new BorderLayout());
    
        // Add the button to the frame. 
        cPane.add (b, BorderLayout.SOUTH);
    
        // Show the frame. 
        this.setVisible (true);
        
    This produces:
    Window
    Note:
    • This time, the button has a "normal" height, but is stretched to occupy all available width.
    • A different version of add is used: a constant is passed as parameter to indicate "South".
    • To see where BorderLayout places items, let's insert five buttons with this code: (source file)
          // Add five buttons at the different locations: 
          JButton b1 = new JButton ("South Quit");
          b1.setBackground (Color.cyan);
          b1.setForeground (Color.magenta);
          cPane.add (b1, BorderLayout.SOUTH);
      
          JButton b2 = new JButton ();
          b2.setBackground (Color.red);
          b2.setFont (new Font ("Serif", Font.PLAIN, 15));
          b2.setText ("North Quit");
          cPane.add (b2, BorderLayout.NORTH);
      
          JButton b3 = new JButton ("<html> East <p> Quit </html>");
          b3.setBackground (Color.blue);
          cPane.add (b3, BorderLayout.EAST);
      
          JButton b4 = new JButton ("West");
          b4.setBackground (Color.green);
          cPane.add (b4, BorderLayout.WEST);
      
          JButton b5 = new JButton ("CENTER");
          b5.setBackground (Color.yellow);
          cPane.add (b5, BorderLayout.CENTER);
          
    • Here is the (ugly) result:
      Window

    NOTE:

    • We can use limited HTML in the text:
          JButton b3 = new JButton ("<html> East <p> Quit </html>");
             
    • We set the Font in a component:
          b2.setFont (new Font ("Serif", Font.PLAIN, 15));
          b2.setText ("North Quit");
             


  2. Next, let's use FlowLayout for these same buttons.
    • The following code is used: (source file)
          // Set the layout manager for our Frame. 
          cPane.setLayout (new FlowLayout());
      
          // Add five buttons at the different locations: 
          JButton b1 = new JButton ("South Quit");
          b1.setBackground (Color.cyan);
          b1.setForeground (Color.magenta);
          cPane.add (b1);
      
          JButton b2 = new JButton ();
          b2.setBackground (Color.red);
          b2.setFont (new Font ("Serif", Font.PLAIN, 15));
          b2.setText ("North Quit");
          cPane.add (b2);
      
          JButton b3 = new JButton ("<html> East <p> Quit </html>");
          b3.setBackground (Color.blue);
          cPane.add (b3);
      
          JButton b4 = new JButton ("West");
          b4.setBackground (Color.green);
          cPane.add (b4);
      
          JButton b5 = new JButton ("CENTER");
          b5.setBackground (Color.yellow);
          cPane.add (b5);
         
    • Here is the result:
      Window

Next, we will add an event-listener that handles the case when the button is pressed.

Here is the code: (source file)

import java.awt.*;
import javax.swing.*;
import java.awt.event.*; // Needed for ActionListener. 

class NewFrame extends JFrame implements ActionListener {

  // Constructor. 
  public NewFrame (int width, int height)
  {
    // Set the title and other frame parameters. 
    this.setTitle ("Button example");
    this.setResizable (true);
    this.setSize (width, height);

    Container cPane = this.getContentPane();

    // Create a new button. 
    JButton b = new JButton ("<html> <b> Quit </b> </html>");
    b.setBackground (Color.red);

    // Add an ActionListener to the button. 
    b.addActionListener (this);

    // BorderLayout is already the manager of the content pane 
    // but leave a comment as a reminder. 
    // cPane.setLayout (new BorderLayout()); 

    // Add the button to the south of the frame. 
    cPane.add (b, BorderLayout.SOUTH);

    // Show the frame. 
    this.setVisible (true);
  }

  // This method is required to implement the  
  // ActionListener interface. 

  public void actionPerformed (ActionEvent a)
  {
    System.out.println ("In actionPerformed");

    // Button must have been pressed - so really quit. 
    System.exit (0);
  }

} // End of class "NewFrame" 


public class TestSwing22 {

  public static void main (String[] argv)
  {
    NewFrame nf = new NewFrame (300, 200);
  }

}
  
Here is the result:
Window

Note:

  • JButton inherits a method called addActionListener.

  • Exactly what is an ActionListener?
    • The definition can be found in package java.awt.event:
          public interface ActionListener extends EventListener {
            public abstract void actionPerformed (ActionEvent e);
          }
              
    • Note: interface EventListener is empty.
    • Thus, an implementor of ActionListener needs to implement only one method: ActionPerformed().
    • This method will be called whenever an "action" takes place.
    • In the above case, the method is called when the button is pressed.


  • We have made our frame (NewFrame) itself implement the ActionListener interface.

  • Since NewFrame is an ActionListener, we register it with our button:
        // Add an ActionListener to the button.  
        b.addActionListener (this);
       
    This tells the button, that the listener has a method called ActionPerformed that can be called when the button is pressed.

  • Note that ActionPerformed is actually called when the button is pressed:
    • The only thing that needs to be done is to actually quit.
    • The parameter ActionEvent contains some information (that is not needed in this case).
    • This information can be used as follows (an alternative to method ActionPerformed():
        public void actionPerformed (ActionEvent a)
        {
          String event_desc = a.getActionCommand();
          if (event_desc.equalsIgnoreCase("Quit"))
            System.exit (0);
        }
           
    • Here, we make doubly sure that it is the Quit button that generated the event.
    • If multiple buttons are used, we can distinguish between them, e.g.,
        public void actionPerformed (ActionEvent a)
        {
          String event_desc = a.getActionCommand();
          if (event_desc.equalsIgnoreCase("Quit"))
            System.exit (0);
          else if (event_desc.equalsIgnoreCase ("Refresh")) {
            // Whatever... 
          }
        }
            

In-Class Exercise 9.7: Use the following template to create a frame:

  1. Create a JPanel and install it in the CENTER of the frame's content pane. Write "Hello World!" at location 100,100.
  2. Create a "Quit" button and install it in the SOUTH location - quit when this button is pressed.
  3. Create a "Zoom" button - double the font size of the "Hello World" string in the panel when this button is pressed.
Thus, you need to add lines to ActionPerformed() to change the fontsize in the panel. Then, you need to call repaint() at the end of ActionPerformed. Finally, you need to implement the method paintComponent (as we have done before) to actually draw the string "Hello World!" in the desired size. Explain where polymorphism was used in this example.


Interaction: mouse clicks

We will now write a simple program to perform some drawing based on mouse-clicks:

  • Whenever the user clicks the mouse inside the frame, a line is drawn from the last click-place to the current click place.
  • The starting point for the first segment will be (0,0).
  • The frame will have a quit button.
To "listen" for mouse-clicks, we need an object that implements the MouseListener interface:
  • Interface MouseListener is defined as:
       public interface MouseListener extends EventListener {
         public abstract void mouseClicked (MouseEvent e);
         public abstract void mouseEntered (MouseEvent e);
         public abstract void mouseExited (MouseEvent e);
         public abstract void mousePressed (MouseEvent e);
         public abstract void mouseReleased (MouseEvent e);
       }
       


  • These five methods have to be overridden to successfully implement the interface.

  • However, we are only interested in mouse-click events.

Here is the code: (source file)

import java.awt.*;
import javax.swing.*;
import java.awt.event.*; // Needed for ActionListener. 

class NewPanel extends JPanel {

  public NewPanel ()
  {
    this.setBackground (Color.cyan);
  }
  
}


class NewFrame extends JFrame 
  implements ActionListener, MouseListener {

  NewPanel panel;

  // Constructor. 
  public NewFrame (int width, int height)
  {
    // Set the title and other frame parameters. 
    this.setTitle ("Button example");
    this.setResizable (true);
    this.setSize (width, height);

    Container cPane = this.getContentPane();
    // cPane.setLayout (new BorderLayout());     

    // The panel for drawing. 
    panel = new NewPanel ();
    cPane.add (panel, BorderLayout.CENTER);

    // Add mouse-listening to panel. 
    panel.addMouseListener (this);

    // Create a quit button. 
    JButton b = new JButton ("Quit");
    b.setBackground (Color.red);
    b.setFont (new Font ("Serif", Font.PLAIN | Font.BOLD, 15));
    b.addActionListener (this);
    cPane.add (b, BorderLayout.SOUTH);

    // Show the frame. 
    this.setVisible (true);
  }

  // This method is required to implement the  
  // ActionListener interface. 
  public void actionPerformed (ActionEvent a)
  {
    // Button must have been pressed - so really quit. 
    System.exit (0);
  }

  // These methods are required to implement  
  // the MouseListener interface. 
  int currentX=0, currentY=0;
  public void mouseClicked (MouseEvent m)
  {
    System.out.println ("mouseClicked event: " + m.paramString());
    int x = m.getX();
    int y = m.getY();
    Graphics g = panel.getGraphics();
    g.drawLine (currentX, currentY, x, y);
    currentX = x;
    currentY = y;
  }

  // We need to implement these methods, but 
  // don't actually have to do anything inside. 
  public void mouseEntered (MouseEvent m) {}
  public void mouseExited (MouseEvent m) {}
  public void mousePressed (MouseEvent m) {}
  public void mouseReleased (MouseEvent m) {}

} // End of class "NewFrame" 


public class TestSwing23 {

  public static void main (String[] argv)
  {
    NewFrame nf = new NewFrame (300, 200);
  }

}
  
The result (after a few random mouse clicks):
Window

Note:

  • To handle mouse events (clicking, dragging, entering etc), a "mouse listener" needs to be passed to the widget that "sees" the events:
    • The mouse events take place in the frame.
    • Thus, we need to pass a "mouse listener" to the panel:
         panel.add ( /* mouse listener */ );
             
    • In our example, the frame itself implements MouseListener.
    • Therefore, we pass the frame itself:
          // Add mouse-listening to the frame itself.  
          panel.addMouseListener (this);
             
    • The MouseListener interface was implemented by overriding all the abstract methods in the interface.
    • The only one we were really interested in was mouseClicked().
    • For the others, we provided empty bodies:
        public void mouseEntered (MouseEvent m) {}
        public void mouseExited (MouseEvent m) {}
        public void mousePressed (MouseEvent m) {}
        public void mouseReleased (MouseEvent m) {}
             


  • The method MouseClicked is called whenever the mouse is clicked within the frame:
    • The parameter MouseEvent carries information, e.g., the pixel coordinates of the place the mouse was clicked.


  • Observe that it is possible to draw in methods other than paintComponent():
    • We drew in the method MouseClicked():
          Graphics g = this.getGraphics();
          g.drawLine (currentX, currentY, x, y);
          
    • To get the Graphics context, simply call the getGraphics() method of Component (inherited by NewPanel).

Now, consider a different version of the program that makes the JPanel the mouselistener: (source file)

import java.awt.*;
import javax.swing.*;
import java.awt.event.*; // Needed for ActionListener. 

class NewPanel extends JPanel implements MouseListener {

  public NewPanel ()
  {
    this.setBackground (Color.cyan);
  }

  // These methods are required to implement  
  // the MouseListener interface. 
  int currentX=0, currentY=0;
  public void mouseClicked (MouseEvent m)
  {
    System.out.println ("mouseClicked event: " + m.paramString());
    int x = m.getX();
    int y = m.getY();
    Graphics g = this.getGraphics();
    g.drawLine (currentX, currentY, x, y);
    currentX = x;
    currentY = y;
  }

  // We need to implement these methods, but 
  // don't actually have to do anything inside. 
  public void mouseEntered (MouseEvent m) {}
  public void mouseExited (MouseEvent m) {}
  public void mousePressed (MouseEvent m) {}
  public void mouseReleased (MouseEvent m) {}
  
}


class NewFrame extends JFrame 
  implements ActionListener {

  NewPanel panel;

  // Constructor. 
  public NewFrame (int width, int height)
  {
    // Set the title and other frame parameters. 
    this.setTitle ("Button example");
    this.setResizable (true);
    this.setSize (width, height);

    Container cPane = this.getContentPane();
    // cPane.setLayout (new BorderLayout());     

    // The panel for drawing. 
    panel = new NewPanel ();
    cPane.add (panel, BorderLayout.CENTER);

    // Add panel's mouse-listening to panel. 
    panel.addMouseListener (panel);

    // Create a quit button. 
    JButton b = new JButton ("Quit");
    b.setBackground (Color.red);
    b.setFont (new Font ("Serif", Font.PLAIN | Font.BOLD, 15));
    b.addActionListener (this);
    cPane.add (b, BorderLayout.SOUTH);

    // Show the frame. 
    this.setVisible (true);
  }

  // This method is required to implement the  
  // ActionListener interface. 
  public void actionPerformed (ActionEvent a)
  {
    // Button must have been pressed - so really quit. 
    System.exit (0);
  }

} // End of class "NewFrame" 


public class TestSwing24 {

  public static void main (String[] argv)
  {
    NewFrame nf = new NewFrame (300, 200);
  }

}
  
Note:
  • The code can be made a little cleaner by moving the listener identification into the JPanel itself:
      public NewPanel ()
      {
        this.setBackground (Color.cyan);
        // Make the panel the listener for the panel. 
        this.addMouseListener (this);
      }
      


Panels as containers

Thus far, we have used panels to draw. Panels are also used as containers for layout purposes.

Suppose we wish to build the following feature into our "click drawing" example:

  • A button called "Orange" which when clicked will change the line color to orange.
  • A button called "Green" which when clicked will change the line color to green.
  • We'll change the background to white.
Thus, this is what we want to observe:
Window

Now, let's look at some code to make this happen: (source file):

import java.awt.*;
import javax.swing.*;
import java.awt.event.*; // Needed for ActionListener. 

class NewPanel extends JPanel implements MouseListener {

  Color drawColor = Color.green;  // Default color. 

  public NewPanel ()
  {
    this.setBackground (Color.white);
    // Add panel's mouse-listening to panel. 
    this.addMouseListener (this);
  }

  // These methods are required to implement  
  // the MouseListener interface. 
  int currentX=0, currentY=0;
  public void mouseClicked (MouseEvent m)
  {
    System.out.println ("mouseClicked event: " + m.paramString());
    int x = m.getX();
    int y = m.getY();
    Graphics g = this.getGraphics();
    g.setColor (drawColor);
    g.drawLine (currentX, currentY, x, y);
    currentX = x;
    currentY = y;
  }

  // We need to implement these methods, but 
  // don't actually have to do anything inside. 
  public void mouseEntered (MouseEvent m) {}
  public void mouseExited (MouseEvent m) {}
  public void mousePressed (MouseEvent m) {}
  public void mouseReleased (MouseEvent m) {}
  
  public void setDrawingColor (Color c)
  {
    this.drawColor = c;
  }

}


class NewFrame extends JFrame 
  implements ActionListener {

  NewPanel drawPanel;

  // Constructor. 
  public NewFrame (int width, int height)
  {
    // Set the title and other frame parameters. 
    this.setTitle ("Button example");
    this.setResizable (true);
    this.setSize (width, height);

    Container cPane = this.getContentPane();
    // cPane.setLayout (new BorderLayout());     

    // The panel for drawing. 
    drawPanel = new NewPanel ();
    cPane.add (drawPanel, BorderLayout.CENTER);

    // Create a quit button. 
    JButton b = new JButton ("Quit");
    b.setBackground (Color.red);
    b.setFont (new Font ("Serif", Font.PLAIN | Font.BOLD, 15));
    b.addActionListener (this);
    cPane.add (b, BorderLayout.SOUTH);

    // Use a panel for clear and start buttons. 
    JPanel topPanel = new JPanel ();
    topPanel.setLayout (new FlowLayout());
    cPane.add (topPanel, BorderLayout.NORTH);

    // Now insert the color buttons. 
    JButton orangeB = new JButton ("Orange");
    orangeB.setBackground (Color.orange);
    orangeB.addActionListener (this);
    topPanel.add (orangeB);
    
    JButton greenB = new JButton ("Green");
    greenB.setBackground (Color.green);
    greenB.addActionListener (this);
    topPanel.add (greenB);

    // Show the frame. 
    this.setVisible (true);
  }

  // This method is required to implement the  
  // ActionListener interface. 
  public void actionPerformed (ActionEvent a)
  {
    String label = a.getActionCommand();
    if (label.equalsIgnoreCase ("Quit"))
      System.exit (0);
    else if (label.equalsIgnoreCase ("Orange"))
      drawPanel.setDrawingColor (Color.orange);
    else if (label.equalsIgnoreCase ("Green"))
      drawPanel.setDrawingColor (Color.green);
  }

} // End of class "NewFrame" 


public class TestSwing25 {

  public static void main (String[] argv)
  {
    NewFrame nf = new NewFrame (300, 200);
  }

}

Note:

  • The panel keeps track of the current color:
    class NewPanel extends JPanel implements MouseListener {
    
      Color drawColor = Color.green;  // Default color. 
    
      // ... 
    
      }
       
  • The setColor() method of Graphics is used to set the drawing color:
        Graphics g = this.getGraphics();
        g.setColor (drawColor);
        g.drawLine (currentX, currentY, x, y);
       
  • We defined a mutator for the panel:
    class NewPanel extends JPanel implements MouseListener {
    
      // ... 
    
      public void setDrawingColor (Color c)
      {
        this.drawColor = c;
      }
    
    }
      
  • The mutator is used when responded to button clicks:
      public void actionPerformed (ActionEvent a)
      {
        String label = a.getActionCommand();
        if (label.equalsIgnoreCase ("Quit"))
          System.exit (0);
        else if (label.equalsIgnoreCase ("Orange"))
          drawPanel.setDrawingColor (Color.orange);
        else if (label.equalsIgnoreCase ("Green"))
          drawPanel.setDrawingColor (Color.green);
      }
      
  • We one panel for drawing:
        drawPanel = new NewPanel ();
        cPane.add (drawPanel, BorderLayout.CENTER);
      
    and one to serve as a container:
        JPanel topPanel = new JPanel ();
        topPanel.setLayout (new FlowLayout());
        cPane.add (topPanel, BorderLayout.NORTH);
    
        // ... 
    
        topPanel.add (orangeB);
        
        // ... 
    
        topPanel.add (greenB);
      
  • The default layout manager for panel's is a FlowLayout so we need not have explicitly set the layout:
        JPanel topPanel = new JPanel ();
        // topPanel.setLayout (new FlowLayout()); 
        cPane.add (topPanel, BorderLayout.NORTH);
      
In-Class Exercise 9.8: Run the above code and compare it with the picture before the code? Fix the code so that it is compatible with the picture. Next, use a BorderLayout for the top-panel instead of its FlowLayout and report what you observe.


The methods paintComponent() and repaint()

The class JComponent defines two methods for use in drawing:

  • paintComponent(): override this to perform drawing.
  • repaint(): call this from outside the component whenever it needs to be re-drawn by the application.

Comparison with AWT:

  • AWT's Component uses paint(), repaint() and update().
  • Never use these methods for Swing components.
    (Unless you are an "expert" designing your own components.)
  • AWT does not perform double-buffering, forcing the programmer to deal with "flickering" (by overriding update()).
  • Swing performs double-buffering.


Applets - a first look

Applets are Java's mechanism for executing code inside a browser.

There are two ways of creating applets:

  1. Extend the class java.applet.Applet:
    • This class was defined in Java 1.0 and refined in Java 1.1.
    • These applets will run in most browsers (Netscape, IE).
    • The HTML required is fairly simple and standardized.
  2. Extend the class javax.swing.JApplet:
    • JApplet subclasses Applet and is much more powerful.
    • These applets do not run in most browsers without the Java Plug-In (a plug-in containing the Swing library).
    • The HTML required for this is complicated because of competing proposals from Netscape and Microsoft.

Some terminology:

  • A Java application is everything we have been doing so far: writing a standalone program that is fired up from the command-line.
  • An applet runs inside a browser.
  • Other non-applications include servlet's.

As an example, let us create an applet version of the mouse-click drawing code we created above.

  • The first step is to create the HTML file (let's call it TestApplet.html):
    <html>
      <body>
    
      An applet to test mouse-clicks:
    
      <applet code="TestApplet.class" width=300 height=200>
    
        <!-- The line below will appear if Java is not supported-->
        Browser does not support Java 1.1.
    
      </applet>
    
      </body>
    </html>
        
    Note:
    • The HTML tag applet is used to invoke an applet.
    • The applet code is specified by the name of the bytecode file, TestApplet.class.
    • The width and height are specified in the HTML command.
    • Any text between the HTML applet-tags will appear in browsers that do not support Java.


  • Next, we need to write the applet code and compile it to the file TestApplet.class in the same directory.

  • To create an applet:
    • Extend the class Applet in package java.applet.
    • Note: Applet is a subclass of Panel and thus, is both a Container and a Component:
      • Thus, Applet is similar to a Frame in that you use paint() to draw.
      • Applet's are different from Frame's in that there is no title and you cannot set the size.
    • Instead of using a constructor, put all initialization code in the method init().
    • Implement listener interfaces as you would for a frame.
    • Do not implement a main method in the applet.


  • Note: there are other important aspects about applets that we will consider later when we go into applets in detail.

Here is the applet code, in TestApplet.java: (source file)

import java.awt.*;
import java.awt.event.*; 
import java.applet.*;      // Must import the Applet class in here.  

public class TestApplet extends Applet
  implements ActionListener, MouseListener {

  // Data. 
  Button quitB, orangeB, greenB;
  Panel p;
  Canvas c;
  int currentX = 0,  currentY = 0;
  Color currentColor = Color.green;

  // Applet's equivalent of constructor. 
  public void init()
  {
    // Title and size cannot be set in an applet. 
    this.setBackground (Color.cyan);

    // Create a quit button: using AWT's Button. 
    quitB = new Button ("Quit");
    quitB.setBackground (Color.red);
    quitB.setFont (new Font ("Serif", Font.PLAIN | Font.BOLD, 15));
    quitB.addActionListener (this);

    // Default for an applet is FlowLayout. 
    this.setLayout (new BorderLayout());
    this.add (quitB, BorderLayout.SOUTH);

    // Create a white canvas using java.awt.Canvas. 
    c = new Canvas ();
    c.setBackground (Color.white);
    c.setForeground (Color.blue);
    c.addMouseListener (this);

    // Add canvas to frame in the center. 
    this.add (c, BorderLayout.CENTER);

    // Create a Panel 
    p = new Panel ();
    p.setLayout (new FlowLayout());
    p.setBackground (Color.white);

    // Create a clear button. 
    orangeB = new Button ("Orange");
    orangeB.addActionListener (this);
    orangeB.setBackground (Color.orange);

    // Create a start button. 
    greenB = new Button ("Green");
    greenB.addActionListener (this);
    greenB.setBackground (Color.green);

    // Add start and clear buttons to panel. 
    p.add (orangeB);
    p.add (greenB);

    // Now add the panel to the frame. 
    this.add (p, BorderLayout.NORTH);

    // Show the frame. 
    this.setVisible (true);
  }

  // This method is required to implement the  
  // ActionListener interface. 
  public void actionPerformed (ActionEvent a)
  {
    String s = a.getActionCommand();
    if (s.equalsIgnoreCase ("Quit"))
      System.exit(0);
    else if (s.equalsIgnoreCase ("Orange")) {
      currentColor = Color.orange;
    }
    else if (s.equalsIgnoreCase ("Green")) {
      currentColor = Color.green;
    }
  }


  // These methods are required to implement  
  // the MouseListener interface. 
  public void mouseClicked (MouseEvent m)
  {
    int x = m.getX();
    int y = m.getY();
    Graphics g = c.getGraphics();
    g.setColor (currentColor);
    g.drawLine (currentX, currentY, x, y);
    currentX = x;
    currentY = y;
  }

  // We need to implement these methods, but 
  // don't actually have to do anything inside. 
  public void mouseEntered (MouseEvent m) {}
  public void mouseExited (MouseEvent m) {}
  public void mousePressed (MouseEvent m) {}
  public void mouseReleased (MouseEvent m) {}

} // End of class "TestApplet" 

To execute the applet, there are several options:

  • You can use the standalone program appletviewer and give it the HTML file:
       % appletviewer TestApplet.html
        
    This produces a window containing the applet. For example, here is what we get after a few mouse-clicks:
    Window


  • Alternatively, you can use a browser that supports Java 1.1. For example, here is what you get with Netscape (4.0 and higher):
    Window


  • Note: SUN's hotjava browser supports Java 1.1, and actually is a browser written in Java.