Posted

15 April 2008 @ 4pm

 

Java Paint: Part 1

Java includes a load of libraries for creating applications with graphical user interfaces (GUI). One of the first things that a programming student is introduced to when learning about Java GUI development is drawing shapes onto a “canvas”. In this post I am going to talk about Java Swing objects that can help achieve this and begin to flesh out a little program that demonstrates some of the ideas that are covered.

This is a really good place to start for some intermediate Java topics and we will cover:

  • Inheritance
  • Collections
  • Double buffering
  • Polymorphism

So before we get going here is a little disclaimer; the following code is an example of how to go about implementing the requirements. It is not the only way or the right way. Second, I am not a Java guru, expert, oracle etc. I just like to tinker with stuff and thought I would share my findings to help others.

So lets begin, first up we need some form of specification. What do we actually want the program to do? I want the user to be able to select a type of shape (Circle or Square) and be able to click on the window of the application and draw the specified shape. I also want everything that the user draws to remain on the window when the window is moved or minimized. The window should be of a specific size and the user should not be able to maximize the window.

Did you get all that? so the fist task might be to dig all of the program features out of this brief paragraph. One thing that quickly becomes apparent however is the large amount of stuff that is not specified. This is a real pain when trying to design a program as the person that list the requirements will usually have a good idea of what they want it to do and will focus on that. This leaves a load of small decisions that still need to be made. In this example there are a few, take the shapes for example. The different types have been specified (square and circle), but the size, color and border are anyones guess. Also the method that the user has to select a specific shape is not specified.

As you can see even with a relatively small program the requirements can be large. For the sake of this little tutorial though I will finish with the specification as I want to focus on the way Java works and how to go about implementing this in Java. You will see how the specification is fulfilled and any other requirements decided further into the post.

As this is part 1 I am only going to implement a limited set of features. I have decided to split it as there are a number of things to take in and it would turn into the longest post in the world and become very boring and hard to follow if I crammed everything into one post. So in this post I will cover setting up a frame and a canvas, mouse interaction with the canvas, and drawing onto the canvas. I will then put this all together and produce a program that can draw a square.

The Frame

The following code is taken from here and is adapted for this example. The class MainFrame creates a window on the screen. This is the window that everything else will happen within. This is an important part of the application because it interacts with the underlying operating system and handles all the displaying stuff so we don’t have to worry about it.

The main method is used to get everything up and running by calling the createAndShowGUI() method which contains all the code to specify how we want our frame to look and behave.


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

public class MainFrame {

    private static void createAndShowGUI() {

        // Create and set up the window.
        JFrame frame = new JFrame("A Frame");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // Set frame size
        frame.setSize ( 300, 350 );
        // prevent frame from maximising
        frame.setResizable (false);
        // center the frame on the screen
        frame.setLocationRelativeTo (null);
        // display it
        frame.setVisible(true);
    }

    public static void main(String[] args) {

        // Schedule a job for the event-dispatching thread:
        // creating and showing this application’s GUI.
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }
}

Compiling and running this code should give you an empty window in the center of your screen like the one pictured below.

mainFrame.jpg

For a bit more info on what is going on in the main method check out this forum post.

The Canvas

As well as the JFrame class Java Swing also provides a JPanel class. The JPanel is described as a lightweight container panel. Basically this means you can use it to contain other controls or other objects. What we are going to use it for in this example is the canvas which the user paints on. This will be where the majority of our applications logic will be held as the canvas will be responsible for listening for user input and the dealing with it should the users perform an action.

In order the get a picture of what this is imagine that the JFrame created in the previous example is a blank table and the JPanel is a clean piece of paper we are going to lay on top of it to draw on. The image below shows are frame with a JPanel covering the JFrames content pane. The content pane is the part of the JFrame that we are going to put are JPanel in. In the picture the JPanel has been colored white as by default its transparent.

mainFrame_with_panel.jpg

Because we need the JPanel to react to user input and we will also want to override a few of its method (you will just have to trust me on this for now). I am going to create a sub class of JPanel called CanvasPanel. This is were all are code will go.

In order to detect the user clicking onto the CanvasPanel we need to add a MouseAdapter and a MouseMotionAdapter to the CanvasPanel. This can be done in the constructor. A small demo version of the CanvasPanel class is shown below.


import java.awt.Color;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import javax.swing.JPanel;

class CanvasPanel extends JPanel {

    public CanvasPanel () {

        this.setBackground ( Color.WHITE );

        // add the input listners
        addMouseListener ( new MouseAdapter () {
            public void mousePressed ( MouseEvent e ) {
                System.out.println ("Mouse Pressed: X = " +
                    e.getX () + ", Y = " + e.getY () );
            }
        });

        addMouseMotionListener ( new MouseMotionAdapter () {
            public void mouseDragged ( MouseEvent e ) {
                System.out.println ("Mouse Dragged: X = " +
                    e.getX () + ", Y = " + e.getY () );
            }
        });
    }
}

As you can see its pretty simple at the moment. All we have is a constructor that sets the background color to white and then adds are mouse input listners. Within these two listners I have stuck a couple of rudimenty console output lines to test that its all working. In order to get our MainFrame object using this fancy new CanvasPanel we need to update the createAndShowGUI method to this:


private static void createAndShowGUI () {
    // Create and set up the window.
    JFrame frame = new JFrame ( "A Frame" );
    frame.setDefaultCloseOperation ( JFrame.EXIT_ON_CLOSE );

    /*
     here is the new line
    */
    frame.getContentPane().add(new CanvasPanel());

    // Display the window.
    frame.setSize ( 300, 350 );
    // prevent frame from maximising
    frame.setResizable ( false );
    // center the frame on the screen
    frame.setLocationRelativeTo ( null );
    frame.setVisible ( true );
}

So as discussed previously we a telling our MainFrame object to use our CanvasPanel by adding a new CanvasPanel object to the MainFrame’s ContentPane. Running this should display a frame that looks the same as before but when you click on it the X and Y coordinates are printed to the console.

The Shapes

So you want to add a shape? Well yes of course you do or you would not have bothered to read this far. A shape is an interesting thing in Java and I advise you to have dig around in the libraries to see what is already available. To cut a long story short Java already has a pretty good shape class hierarchy in the standard library. I know is a favorite topic for lecturers to use when talking about inheritance and its all in the Java library. The majority of objects that Java specifies for representing shapes impliment the Shape interface. In this iteration of the example we want to focus on the Rectangle class.

Before we can start painting however we need to understand a few other things about the CanvasPanel object we created earler. What we need is to override the paintComponent within CanvasPanel. This method is called every time something is updated or changed like the location of the frame on the screen or another application’s window passes in front of our window. The paintComponent method is passed a Graphics object. A quick look at the documentation for this and we find a number of very usefull sounding methods like drawRect() and drawOval(). The Graphics object is the thing we need. Everything we want to display we draw on this object.

Now before we get going with our rectangle I need to talk about a few things. What I want to happen is that when a user clicks on the CanvasPanel a new square is drawn. If the user drags the mouse over the panel then a line of squares should be drawn. This raises a few questions. How do I remeber where all the squares are meant to be and how do I triger the paintComponent method myself?

First up I think we need a Square object to see what we are talking about:


import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;

public class Square extends Rectangle {
    int SIZE = 20;
    Color squareColor = Color.RED;  

    public Square ( int x, int y ) {
        super();
        this.x = x;
        this.y = y;
        this.height = SIZE;
        this.width = SIZE;
    }

    public void paintMe ( Graphics g ) {
        g.setColor ( squareColor );
        g.fillRect ( x, y, width, height );
    }
}

I have extended the Java Rectangle class and have added a special method called paintMe to my Square which takes a Graphics objects and draws a square onto it. We now have to add a little bit to the CanvasPanel in order for it to be able to store the Square objects and pass a Graphics object to them. First off I need an array to hold all of the Square objects that are created by the user. Initially you may be thinking easy, declare an Array, no problem. Thinking about it though this is not a good idea mainly because we do not know how big the array needs to be. The user may draw 1 Square or 1000 so we need a container object that can handle this. Introducing the ArrayList. With an array list we don’t need do declare a size as it has the ability to shrink and grow depending on what is added or removed from it.

So every time we detect mouse activity on the CanvasPanel we create a new shape and add it to the ArrayList. Then we call the repaint() method which trigers the repainting of the Canvas and draws the new shape. This is done by overriding the paintComponent() method and looping through the ArrayList and calling the paintMe() method of each shape. All of these modifications are shown in the updated version of the CanvasPanel class below:


import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.util.ArrayList;
import javax.swing.JPanel;

class CanvasPanel extends JPanel {

    // declare arraylist to store shapes
    ArrayList<Square> s = new ArrayList<Square>();

    public CanvasPanel () {

        this.setBackground ( Color.WHITE );

        // add the input listners
        addMouseListener ( new MouseAdapter () {
            public void mousePressed ( MouseEvent e ) {
                newSquare ( e.getX (), e.getY () );
            }
        } );

        addMouseMotionListener ( new MouseMotionAdapter () {
            public void mouseDragged ( MouseEvent e ) {
                newSquare ( e.getX (), e.getY () );
            }
        } );
    }

    private void newSquare ( int x, int y ) {

        // add new square to list
        s.add(new Square( x, y ));

        // repaint screen
        repaint ();
    }

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

        // loop through each square
        for(Square tmp:s)
            // paint it
            tmp.paintMe(g);
    }
}

As you can see every time input is detected a call to newSquare() is made with the X and Y of the input. A new Square is created and added to the ArrayList and then a call to repaint() is made. This invokes the paintComponent() method which loops through all the shapes and passes them the Graphics object for each Square to paint its self onto.

Tidy up

If you run the program now you should be able to draw squares onto the canvas, but there is one small issue in that the square is drawn from top left corner. I want the mouse pointer to be in the middle of the square when it is drawn. To do this we simple change the way the Square object interprets the X and Y values that it’s given by offsetting them by half of the squares length so that the constructer now looks like this:


public Square ( int x, int y ) {
    super();
    this.x = x - (SIZE/2);
    this.y = y - (SIZE/2);
    this.height = SIZE;
    this.width = SIZE;
}

Now run the program and the squares are nicely centered on the pointer.

squares.jpg

Thats a wrap

That’s it for part 1. In the next part I will cover double buffering.

Resources

You can download the source files for part 1 here.

Here are some of the books and web pages that I found really useful in trying to understand this topic:

Books

Web Pages

 

No Comments Yet

There are no comments yet. You could be the first!

Leave a Comment