Graphics

JFC

JFC is a collection of packages that provide GUI and graphics components:

awt = peer-based controls & event handling
swing = peerless controls
accessibility API
2D Graphics API
drag-and-drop API

These notes will focus on building GUIs using swing controls and awt event handling.

There are two types of GUI components (buttons, menus, windows, etc.): peer-based and peerless. A peer-based component delegates commands to an associated system component. Using peer-based components can lead to subtle but annoying differences in the look and feel of a GUI from one platform to another. A peerless component is just a picture of a control in a window. The behavior of the control is completely determined by the Java VM. JComponent is the base class of all peerless components. JFrame and JDialolg are the only peer-based swing components:

GUI Components

We can view a graphical user interface (GUI) as a tree of GUI components. Parent nodes are containers that hold their children. Leaf nodes are views and controls:

A container is a GUI component that holds other GUI components, including other containers! The two main types of containers are windows and panels. A window has a border, a panel doesn't. Panels are useful for grouping components within a container.

The two main types of windows are dialog boxes and frames. If we view a GUI as a tree, then the root of the tree would usually be a frame. In other words, a frame is usually the main window of the application. Dialog windows are child windows that appear when user input is required or when a message needs to be displayed to the user.

Menu bars, menus, toolbars, applets, and control panels are examples of special purpose windows.

There are two types of controls. Input controls receive user input, while output controls display program output.

Input Controls
   Button (JButton)
   Combo Box (JComboBox)
   List (JList)
   Menu (JMenu)
   Slider (JSlider)
   Text Field (JTextField)
   Text (JTextArea)
   Tree (JTree)
   Color Chooser (JColorChooser)
   File Chooser (JFileChooser)
   Table (JTable)

Output Controls
   Label (JLabel)
   Progress Bar (JProgressBar)
   Tool Tip (JToolTip)

GraphicsDemo

The basic application window is moveable, resizable, has a title bar, and minimize, maximize, and close buttons:

Structurally, an application window is a JPanel contained in a JFrame:

GraphicsDemo.java begins by importing the most commonly used JFC packages:

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

The GraphicsDemo class extends MainJFrame, which is simply a closeable JFrame (we will describe this class in detail, later). The constructor sets the title, then adds a graphics panel to the content pane of the JFrame:

public class GraphicsDemo extends jutil.MainJFrame {
   public GraphicsDemo() {
      setTitle("Graphics Demo");
      Container contentPane = getContentPane();
      JPanel panel = new GraphicsPanel();
      contentPane.add(panel);
   }

The main() method creates a GraphicsDemo instance, then calls its show() method. This method causes the window to display itself on the desktop and begin listening for user input events such as mouse and keyboard events:

   public static void main(String[] args) {
      JFrame demo = new GraphicsDemo();
      demo.show();
   }
} // GraphicsDemo

The GraphicsPanel extends Swing's JPanel class. The programmer can implement a paintComponent() method to draw in the panel:

class GraphicsPanel extends JPanel {
   public void paintComponent(Graphics g) {
      super.paintComponent(g);
      // graphics code goes here
   }
}

JFrame Structure

A JFrame consists of four panes: root pane, layered pane, content pane, and glass pane:

Components are added to the content pane, although we will usually add a JPanel to the content pane and then add controls to the JPanel.

Graphical Contexts

A graphical context is an instance of the Graphics class. We can think of a graphical context as an abstraction of a graphics device. It provides a blank canvas (i.e., a bitmap) and tools for drawing curves, shapes, and text:

class Graphics {
   void drawLine(...) { ... }
   void drawOval(...) { ... }
   void drawString(...) { ... }
   // etc.
}

Each time a component, c, needs to be redrawn, a graphical context for c is created and passed to the component's update method:

Graphics g = c.getGraphics();
c.update(g);

Component.update() erases the background of the window, then calls:

c.paintComponent(g);

We can force a call to Component.update() from within the program by calling:

c.repaint();

The paintComponent() method is already defined for controls and containers. Programmers redefine paintComponent() for components to add text, images, and shapes to the graphical context.

Client Area

The client area is the region of a container excluding the borders and title bar. This is where client drawings actually appear. Points in the client area are identified by their coordinates in a coordinate system in which the positive x-axis runs across the top of the container and the positive y-axis runs down the left side of the container. The unit of measurement is the PIXEL.

Fonts

A font face name, or font name, has the form "FAMILY STYLE". For example:

"Helvetica Bold"

We create a font object using the Font constructor:

Font(FAMILY, STYLE, SIZE)

FontDemo

FontPanel Class

class FontPanel extends JPanel {
   // Pre-defined logical font names (new and old):
   String[] fonts = {
      "SanSerif", "Serif", "Monospaced", "Dialog", "DialogInput",
      "Helvetica", "Courier", "ZapfDingbats"
   };
   // A few pre-defined styles:
   int[] styles = {
      Font.PLAIN, Font.ITALIC, Font.BOLD, Font.BOLD + Font.ITALIC
   };

   public void paintComponent(Graphics g) {
      super.paintComponent(g);
      Font font;
      FontMetrics fm;
      int row = 10;
      int points = 10;
      for(int i = 0; i < fonts.length; i++) {
         int col = 1;
         int height = 0;
         for(int j = 0; j < styles.length; j++) {
            // use next font to display its name:
            font = new Font(fonts[i], styles[j], points);
            g.setFont(font);
            g.drawString(fonts[i], col, row);
            // update height and row:
            fm = g.getFontMetrics(font); 
            height = Math.max(height, fm.getHeight());
            col += (fm.stringWidth(fonts[i]) + 2);
         }
         row += height + 2; // = next row
      }
   }
}

FontDemo Class

public class FontDemo extends jutil.MainJFrame {
     
   public FontDemo() {
      setTitle("Font Demo");
      Container contentPane = getContentPane();
      contentPane.add(new FontPanel());
   }
  
   public static void main(String[] args) {
      FontDemo demo = new FontDemo();
      demo.show();
   }
}

Color

ColorDemo

ColorPanel Class

class ColorPanel extends JPanel {
   String[] message = {
      "Mercury", "Venus", "Earth", "Mars", "Jupiter",
      "Saturn", "Neptune", "Uranus", "Pluto"
   };
   java.util.Random generator = new java.util.Random();
  
   public void paintComponent(Graphics g) {
      super.paintComponent(g);
      int col = 10, row = 10;
      int red = 255, green = 0, blue = 0;
      for(int i = 0; i < message.length; i++) {
         g.setColor(new Color(red, green, blue));
         g.drawString(message[i], col, row);

         red = generator.nextInt(256);
         green = generator.nextInt(256);
         blue = generator.nextInt(256);
         row += 20;
      }
   }
}

ColorDemo Class

public class ColorDemo extends jutil.MainJFrame {
   public ColorDemo() {
      setTitle("Color Demo");
      // setBackground(Color.yellow);
      Container contentPane = getContentPane();
      contentPane.add(new ColorPanel());
   }
  
   public static void main(String[] args) {
      ColorDemo demo = new ColorDemo();
      demo.show();
   }
}

Shapes

A graphical context provides methods for drawing rectangles:

Here is the listing for this image:

class DrawDemoPanel extends JPanel {
   public DrawDemoPanel() {
      setBackground(Color.white);
   }
   public void paintComponent(Graphics g) {
      super.paintComponent(g);
      int corner = 20;
      int size = 300;
      int dec = 20;
      g.setColor(Color.gray);
      g.fill3DRect(corner, corner, size, size, true);
      corner += dec;
      size -= 2 * dec;
      g.fill3DRect(corner, corner, size, size, false);
      g.setColor(Color.green);
      corner += dec;
      size -= 2 * dec;
      g.fillRect(corner, corner, size, size);
      g.setColor(Color.red);
      corner += dec;
      size -= 2 * dec;
      g.drawRect(corner, corner, size, size);
   }
}

A graphical context provides methods for drawing ovals:

Here is a listing for this image:

class DrawDemoPanel extends JPanel {
   public DrawDemoPanel() {
      setBackground(Color.white);
   }
   public void paintComponent(Graphics g) {
      super.paintComponent(g);
      g.setColor(Color.blue);
      g.fillOval(10, 10, getWidth() - 20, getHeight() - 20);
      g.setColor(Color.red);
      g.fillOval(40, 40, getWidth() - 80, getHeight() - 80);
      g.setColor(Color.yellow);
      g.fillOval(70, 70, getWidth() - 140, getHeight() - 140);
   }
}

Metrics

Metrics Demo

MetricPanel Class

public class MetricPanel extends JPanel {
   // client area metrics:
   protected int xUpperLeft, yUpperLeft, xLowerRight, yLowerRight;
   protected int clientHeight, clientWidth, xCenter, yCenter;
   // screen metrics:
   protected int screenHeight, screenWidth;
   protected int xScreenCenter, yScreenCenter;   
   protected boolean drawAxes = false;
   public void setDrawAxes(boolean flag) {
      drawAxes = flag;
   }

   protected void updateMetrics() {
      Dimension d = getSize();
      Insets in = getInsets();
      clientWidth = d.width - in.right - in.left;
      clientHeight = d.height - in.bottom - in.top;
      xUpperLeft = in.left;
      yUpperLeft = in.top;
      xLowerRight = xUpperLeft + clientWidth;
      yLowerRight = yUpperLeft + clientHeight;
      xCenter = xUpperLeft + clientWidth/2;
      yCenter = yUpperLeft + clientHeight/2;

      Toolkit tk = Toolkit.getDefaultToolkit();
      d = tk.getScreenSize();
      screenHeight = d.height;
      screenWidth = d.width;
      xScreenCenter = screenWidth/2;
      yScreenCenter = screenHeight/2;
   }

   public void drawAxes(Graphics g) {
      g.setColor(Color.black);
      g.drawLine(xCenter, yUpperLeft, xCenter, yLowerRight);
      g.drawLine(xUpperLeft, yCenter, xLowerRight, yCenter);
   }

   public void paintComponent(Graphics g) {
      super.paintComponent(g);
      updateMetrics();
      if (drawAxes) {
         drawAxes(g);
         g.setColor(Color.black);
         g.drawRect(xUpperLeft + 1, yUpperLeft + 1,
            clientWidth - 2, clientHeight - 2);
         g.drawOval(xCenter - 5, yCenter - 5, 10, 10);
         g.fillOval(xUpperLeft, yUpperLeft, 5, 5);
         g.fillOval(xLowerRight - 5, yLowerRight - 5, 5, 5);
      }
   }
}

GraphicsDemo Class

public class MetricsDemo extends jutil.MainJFrame {
   public MetricsDemo() {
      setTitle("Metrics Demo");
      Container contentPane = getContentPane();
      MetricPanel mp = new MetricPanel();
      mp.setDrawAxes(true);
      contentPane.add(mp);
   }
  
   public static void main(String[] args) {
      JFrame demo = new MetricsDemo();
      demo.show();
   }
}

Graphing

Functors

interface Functor {
   double apply(double x);
}

class SineFunctor implements Functor {
   public double apply(double x) {
      return Math.sin(x);
   } 
}

class SquareFunctor implements Functor {
   public double apply(double x) {
      return x * x;
   }
}

GraphPanel Class

class GraphPanel extends MetricPanel {
   Functor fun = null;
   private int xRatio = 100;
   private int yRatio = 100;
   public void setRatio(int x, int y) {
      xRatio = x;
      yRatio = y;
   }
   public void setFunctor(Functor f) {
      fun = f;
   }

   public void paintComponent(Graphics g) {
      super.paintComponent(g);
      updateMetrics();
      drawAxes(g);
      if (fun != null) {
         for(int xc = 0; xc < clientWidth; xc++) {
            double x = (double)(xc - xCenter)/(double)xRatio;
            double y = fun.apply(x);
            int yc = yCenter - (int)(y * yRatio);
            g.fillOval(xc - 1, yc - 1, 3, 3);
         }
      }
   }
}

GraphDemo Class

public class GraphDemo extends jutil.MainJFrame {
   public GraphDemo() {
      setTitle("Graph Demo");
      Container contentPane = getContentPane();
      GraphPanel gp = new GraphPanel();
      gp.setFunctor(new SineFunctor());
      gp.setRatio(30, 100);
      contentPane.add(gp);
   }
   public static void main(String[] args) {
      JFrame demo = new GraphDemo();
      demo.show();
   }
}

PolyDemo

PolyPanel Class

class PolyPanel extends MetricPanel {
   private int red = 255, green = 0, blue = 0;
   private int sides = 5;
   public void setSides(int s) {
      sides = s;
   }
   public void setColor(int r, int g, int b) {
      red = r;
      green = g;
      blue = b;
   }

   public void paintComponent(Graphics g) {
      super.paintComponent(g);
      updateMetrics();
      g.setColor(new Color(red, green, blue));
      Polygon p = new Polygon();
      double radius = Math.min(clientHeight, clientWidth)/2;
      double angle = 2 * Math.PI/sides;
      for(int i = 0; i < sides; i++)
         p.addPoint(
            (int)(xCenter + radius * Math.cos(i * angle)),
            (int)(yCenter + radius * Math.sin(i * angle)));
      g.drawPolygon(p);
      g.setColor(Color.black);
      String s = "# sides = " + sides;
      FontMetrics fm = g.getFontMetrics();
      int w = fm.stringWidth(s);
      int h = fm.getHeight();
      g.drawString(s, xCenter - w/2, yCenter - h/2);
   }
}

PolyDemo Class

public class PolyDemo extends jutil.MainJFrame {
   public PolyDemo() {
      setTitle("Poly Demo");
      Container contentPane = getContentPane();
      PolyPanel pp = new PolyPanel();
      pp.setColor(255, 0, 255);
      pp.setSides(9);
      contentPane.add(pp);
   }
   public static void main(String[] args) {
      JFrame demo = new PolyDemo();
      demo.show();
   }
}