Java Notes

Example - PaintDemo

This simple paint applet / application leaves much to be desired, but it illustrates a few things:
  • Event-driven programming.
  • Mouse listeners.
  • Use of paintComponent()
  • Interaction between listeners and paintComponent() using instance variables.

BufferedImage. The standard Graphics class methods are used with two additions. The BufferedImage object is used to accumulate the effect of all drawing. The paintComponent() method first draws the BufferedImage, then draws the current shape, which may be changing as the mouse is being dragged When the mouse is released, the current shape is drawn into the BufferedImage..

Graphics2D. The Graphics2D class allows more extensive graphics operations than the Graphics class. In this case it's needed to work with the BufferedImage. Because Graphics2D is a child class (subclass) of Graphics, it's easy to downcast the Graphics object that is passed to paintComponent(). After downcasting, all Graphics2D operations become available.

Source code

The source code is dividing into three classes:

  1 
  2 
  3 
  4 
  5 
  6 
  7 
  8 
  9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
 41 
 42 
 43 
 44 
 45 
 46 
 47 
 48 
 49 
 50 
 51 
 52 
 53 
 54 
 55 
 56 
 57 
 58 
 59 
 60 
 61 
 62 
 63 
 64 
 65 
 66 
 67 
 68 
 69 
 70 
 71 
 72 
 73 
 74 
 75 
 76 
 77 
 78 
 79 
 80 
 81 
 82 
 83 
 84 
 85 
 86 
 87 
 88 
 89 
 90 
 91 
 92 
 93 
 94 
 95 
 96 
 97 
 98 
 99 
100 
101 
102 
103 
104 
105 
106 
107 
108 
109 
110 
111 
112 
113 
114 
115 
// File   : gui-lowlevel/paintdemo/PaintDemo.java
// Purpose: A simple painting program.
//          Illustrates use of mouse listeners and BufferedImage.
//          Runs as both applet and main program.
// Author : Fred Swartz 2002, 2006-10-13 - Placed in public domain.
// Possible Enhancements:
//   * Clear drawing area
//   * Other shapes.
//   * An eraser (define new shape, draw on buffered image during drag).

package paintdemo;

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

////////////////////////////////////////////////////////////////////// PaintDemo
public class PaintDemo extends JApplet {
    //=================================================================== fields
    PaintPanel _canvas = new PaintPanel();
    
    //===================================================================== main
    public static void main(String[] args) {
        //... Create and initialize the applet.
        JApplet theApplet = new PaintDemo();
        
        //... Create a window (JFrame) and make applet the content pane.
        JFrame window = new JFrame();
        window.setContentPane(theApplet);
        window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        window.setTitle("Paint Demo 2");
        window.pack();
        window.setLocationRelativeTo(null); // Center window.
        window.setResizable(false);
        // System.out.println(theApplet.getSize()); // to get applet size.
        window.setVisible(true);            // Make the window visible.
    }
    
    //============================================================== constructor
    public PaintDemo() {
        //... Create radio buttons for shapes.........................
        JRadioButton circleButton = new JRadioButton("Oval");
        circleButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                _canvas.setShape(Shape.OVAL);
            }});
            
        JRadioButton rectangleButton = new JRadioButton("Rectangle", true);
        rectangleButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                _canvas.setShape(Shape.RECTANGLE);
            }});
            
        JRadioButton lineButton = new JRadioButton("Line");
        lineButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                _canvas.setShape(Shape.LINE);
            }});
            
        ButtonGroup shapeGroup = new ButtonGroup();
        shapeGroup.add(circleButton);
        shapeGroup.add(rectangleButton);
        shapeGroup.add(lineButton);
        
        //--- Layout the  shape buttons
        JPanel shapePanel = new JPanel();
        shapePanel.setLayout(new GridLayout(3,1));
        shapePanel.add(circleButton);
        shapePanel.add(rectangleButton);
        shapePanel.add(lineButton);
        
        //... Create radio buttons for colors...............................
        JRadioButton redButton = new JRadioButton("Red", true);
        redButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                _canvas.setColor(Color.RED);
            }});
            
        JRadioButton greenButton = new JRadioButton("Green");
        greenButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                _canvas.setColor(Color.GREEN);
            }});
            
        JRadioButton blueButton = new JRadioButton("Blue");
        blueButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                _canvas.setColor(Color.BLUE);
            }});
            
        ButtonGroup colorGroup = new ButtonGroup();
        colorGroup.add(redButton);
        colorGroup.add(greenButton);
        colorGroup.add(blueButton);
        
        //... Layout the color buttons
        JPanel colorPanel = new JPanel();
        colorPanel.setLayout(new GridLayout(3,1));
        colorPanel.add(redButton);
        colorPanel.add(greenButton);
        colorPanel.add(blueButton);
        
        //... Create a panel to hold the separate button panels.............
        JPanel buttonPanel = new JPanel();
        buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS));
        buttonPanel.add(shapePanel);
        buttonPanel.add(colorPanel);
        buttonPanel.add(Box.createHorizontalGlue());
        
        //... layout the applet ...........................................
        setLayout(new BorderLayout(5,5));
        add(buttonPanel, BorderLayout.NORTH);
        add(_canvas    , BorderLayout.CENTER);
    }
}

The graphics panel

  1 
  2 
  3 
  4 
  5 
  6 
  7 
  8 
  9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
 41 
 42 
 43 
 44 
 45 
 46 
 47 
 48 
 49 
 50 
 51 
 52 
 53 
 54 
 55 
 56 
 57 
 58 
 59 
 60 
 61 
 62 
 63 
 64 
 65 
 66 
 67 
 68 
 69 
 70 
 71 
 72 
 73 
 74 
 75 
 76 
 77 
 78 
 79 
 80 
 81 
 82 
 83 
 84 
 85 
 86 
 87 
 88 
 89 
 90 
 91 
 92 
 93 
 94 
 95 
 96 
 97 
 98 
 99 
100 
101 
102 
103 
104 
105 
106 
107 
108 
109 
110 
111 
112 
113 
114 
115 
116 
117 
118 
119 
120 
121 
122 
123 
124 
125 
126 
127 
128 
129 
130 
131 
132 
133 
134 
135 
136 
137 
138 
139 
140 
141 
142 
143 
144 
145 
146 
147 
148 
// File   : gui-lowlevel/paintpanel/PaintPanel.java
// Purpose: Draws graphics, handles mouse events.
//          Has setters for shape and color.
// Author : Fred Swartz - October 12, 2006 - Placed in public domain.
// Possible enhancements:
//          * Allow dragging shapes up or to left, not just down+right.
//                by using max/min of coordinates.

package paintdemo;

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

///////////////////////////////////////////////////////////////////// PaintPanel
class PaintPanel extends JPanel implements MouseListener, MouseMotionListener {
    
    //================================================================ constants
    private static final int SIZE = 300;     // Size of paint area.
    private static final Shape INIIIAL_SHAPE = Shape.RECTANGLE;
    private static final Color INITIAL_COLOR = Color.RED;
    private static final Color COLOR_BACKGROUND = Color.WHITE;
    private enum         State { IDLE, DRAGGING }
    
    //=================================================================== fields
    private State _state = State.IDLE;
    private Shape _shape = INIIIAL_SHAPE;
    private Color _color = INITIAL_COLOR;
    
    private Point _start = null; // Where mouse is pressed.
    private Point _end   = null; // Where mouse is dragged to or released.
    
    //... BufferedImage stores the underlying saved painting.
    //    Initialized first time paintComponent is called.
    private BufferedImage _bufImage = null;
    
    
    //============================================================== constructor
    public PaintPanel() {
        setPreferredSize(new Dimension(SIZE, SIZE));
        setBackground(Color.white);
        
        this.addMouseListener(this);
        this.addMouseMotionListener(this);
    }
    
    //================================================================= setShape
    public void setShape(Shape shape) {
        _shape = shape;
    }
    
    //================================================================= setColor
    public void setColor(Color color) {
        _color = color;
    }
    
    //=========================================================== paintComponent
    @Override public void paintComponent(Graphics g) {
        Graphics2D g2 = (Graphics2D)g;  // Downcast to Graphics2D
        
        //... One time initialization of in-memory, saved image.
        if (_bufImage == null) {
            //... This is the first time, initialize _bufImage
            int w = this.getWidth();
            int h = this.getHeight();
            _bufImage = (BufferedImage)this.createImage(w, h);
            Graphics2D gc = _bufImage.createGraphics();
            gc.setColor(COLOR_BACKGROUND);
            gc.fillRect(0, 0, w, h); // fill in background
        }
        
        //... Display the saved image.
        g2.drawImage(_bufImage, null, 0, 0);
        
        //... Overwrite the screen display with currently dragged image.
        if (_state == State.DRAGGING) {
            //... Write shape that is being dragged over the screen image,
            //    but not into the saved buffered image.  It will be written
            //    on the saved image when the mouse is released.
            drawCurrentShape(g2);
        }
    }
    
    //========================================================= drawCurrentShape
    private void drawCurrentShape(Graphics2D g2) {
        //... Draws current shape on a graphics context, either
        //    on the context passed to paintComponent, or the
        //    context for the BufferedImage.
        
        g2.setColor(_color);    // Set the color.
        
        switch (_shape) {
            case OVAL:
                g2.fillOval(_start.x, _start.y, _end.x - _start.x, _end.y - _start.y);
                break;
                
            case RECTANGLE:
                g2.fillRect(_start.x, _start.y, _end.x - _start.x, _end.y - _start.y);
                break;
                
            case LINE:
                g2.drawLine(_start.x, _start.y, _end.x  , _end.y);
                break;
                
            default:  // Should never happen!
                g2.drawString("Huh?", 10, 20);
                break;
        }
    }
    
    //============================================================= mousePressed
    public void mousePressed(MouseEvent e) {
        _state = State.DRAGGING;   // Assume we're starting a drag.
        
        _start = e.getPoint();     // Save start point, and also initially
        _end   = _start;           // as end point, which drag will change.
    }
    
    //============================================================= mouseDragged
    public void mouseDragged(MouseEvent e) {
        _state = State.DRAGGING;   // We're dragging to create a shape.
        
        _end = e.getPoint();       // Set end point of drag.  May change.
        this.repaint();            // After change, show new shape
    }
    
    //============================================================ mouseReleased
    public void mouseReleased(MouseEvent e) {
        //... If released at end of drag, write shape into the BufferedImage,
        //    which saves it in the drawing.
        _end = e.getPoint();      // Set end point of drag.
        if (_state == State.DRAGGING) {
            _state = State.IDLE;
            
            //... Draw current shape in saved buffered image.
            drawCurrentShape(_bufImage.createGraphics());
            
            this.repaint();
        }
    }
    
    //================================================== ignored mouse listeners
    public void mouseMoved  (MouseEvent e) {}
    public void mouseEntered(MouseEvent e) {}
    public void mouseExited (MouseEvent e) {}
    public void mouseClicked(MouseEvent e) {}
}

The enum defining possible shapes

  1 
  2 
  3 
  4 
  5 
  6 
  7 
  8 
// File   : gui-lowlevel/paintdemo/Shape.java
// Purpose: Defines the shapes that can be drawn.
// Author : Fred Swartz - October 12, 2006 - Placed in public domain.

package paintdemo;

/////////////////////////////////////////////////////////////////// Shape
public enum Shape { RECTANGLE, OVAL, LINE }

Defining methods associated with enum constants

Whenever you see a switch statement based on an enum value, you might consider whether you can define a method in the enum class for handling this. Such a use of enums goes beyond what is available in many languages, and is quite useful. Here are sections of the above code rewritten in this form.

Simplifying the PaintPanel drawCurrentShape method

Compare the simplicity of this to the above version with a switch.

    //========================================================= drawCurrentShape
    private void drawCurrentShape(Graphics2D g2) {
        g2.setColor(_color);            // Set the color.
        _shape.draw(g2, _start, _end);  // Draw the shape.
    }

Moving the drawing to the enum class

The drawing code has to go somewhere. The separate behaviors of the different shapes is probably more appropriately centralized with each shape as below.

////////////////////////////////////////////////////////////////////////// Shape
public enum Shape {
    RECTANGLE {
        void draw(Graphics2D g2, Point from, Point to) {
            g2.fillRect(from.x, from.y, to.x - from.x, to.y - from.y);
        }
    },
    OVAL {
        void draw(Graphics2D g2, Point from, Point to) {
            g2.fillOval(from.x, from.y, to.x - from.x, to.y - from.y);
        }
    },
    LINE {
        void draw(Graphics2D g2, Point from, Point to) {
            g2.drawLine(from.x, from.y, to.x, to.y);
        }
    };
    
    //... Must define methods implemented by all constants as abstract.
    //    Note semicolon after last enum constant above.
    abstract void draw(Graphics2D g2, Point from, Point to);
}