Csci 210 Lab 2: Drawing

(Laura Toma adapted from Eric Chown)

Overview

In this lab you will build an interactive program similar to what you can do in programs like Microsoft Word. You'll use rectangles, ovals, and such, and learn about some graphical features in Java, like buttons and panels. This is the sort of lab that looks much harder than it actually is, because of all the technical requirements of drawing in Java. Most of the work will be to read the documentation pages of the various components you will be using, and to understand how to put them together.

Some doc sites that you'll find useful:

Background

To define GUIs in Java we will use Swing. There are several drawing packages to choose from in Java (the most notable other choice is called awt. We are choosing swing because it works fairly predictibly accross platforms (Macs, Windows, linux, etc). A program can use the Swing package by including the statement:
import javax.swing.*;
at the head of your file. Swing is actually a lot like awt except that almost everything you use has an extra capital J in front of it. For example we will use JFrame class, whereas in awt it would simply be a Frame. We will get to the definition of a JFrame in a moment. In any case it is helpful to know this when looking at your book, which sometimes uses awt. The differences in coding are really minimal.

In Java, when you want to draw something, or build a graphical interface, you need a place to put things. For example you might need a place to do the drawing, another place to put buttons, another to write text to the user, etc. A JFrame is one way to do this. JFrames (and many of the other things we will learn about having to do with drawing) are examples of Components in Java. As the name suggests we can put Components together to form more complex objects. Some components stand on their own (buttons, text areas), other are used to group/hold together components (panels). A GUI consists of components arranged in a hierarchy (one contained in another), and having a top-level container that holds all the individual components together into a window. Swing provides 3 top-level containers (JFrame, JDialog, JApplet). We will be using JFrame.

One of the things we will want to do with Components in this class is to have users interact with them. To do this we need to track things like where the mouse is, when it has been clicked, if a button was clicked, etc. In Java all of these things are called events and we call the things that pay attention to the events, event listeners. We have already seen a kind of event listener that pays attention to mouse events. It is usually the case that whenever you use a Component in Java (like a JButton), you use a corresponding listener for the Component to track when it has been used.

In this lab we will start to learn how to build Components, put them together and create listeners to use them with. Since this is our first experience with "real" Java, and this class is not about Java graphics, we will stick to fairly simple Components (namely JFrames and JButtons).

Your finished program for this lab should have a number of basic capabilities. It should be able to draw Rectangles, Ovals, and Lines using a color chosen by the user. Essentially, you'll specify the location and size of a Rectangle, for example, by using the mouse directly. For example, to draw a Rectangle you would hit the Rectangle button, then press the mouse to select one corner of the rectangle, drag the mouse to the opposite corner of the rectangle and let go. When you let go the rectangle should appear. We will proceed, as always in pieces.

Drawing Lines in a Window

The first thing we need to do is to make a window. Probably the simplest way to do this is to make our program extend the JFrame class. For example

import javax.swing.*
public class MyDrawingProgram extends JFrame 

would do the trick. Our new class will require a constructor of course and this is often where much of the work of an interactive graphical program takes place. Remember, the constructor is a method that is declared like this:

public MyDrawingProgram() 
At least that is a version of the constructor with no parameters. Usually the first line in the constructor should be a call to the constructor of the super-class (JFrame). The JFrame constructor takes a string parameter which is used as the window's title. The corresponding call would be
super("My drawing window!");

JFrames have lots of other useful methods built in that you inherit with the extends call. These include setSize(int x, int y) to set its size, and setVisible(boolean value) to either display it or hide it. One thing to be careful about - you can't actually draw anything within the constructor (afterall the JFrame is still being constructed!).

Within a frame, any drawing commands that are issued implicitly specify that the drawing takes place in the frame. This is accomplished by giving each JFrame its own graphics context. Therefore to draw in a JFrame you will need the correct Graphics context. For example to draw you would issue commands like:

//Note that these commands would come within one of your methods
Graphics g = this.getGraphics(); // gets Graphics context
g.drawRect(x, y, width, height);
g.fillRect(x, y, width, height);
g.setColor(Color.red);  // change the pen color to red
g.drawLine(x, y, x1, y1);
g.fillOval(x, y, width, height);

All of the graphics stuff comes from java.awt. So you'll need an import statement like

 import java.awt.*; 
which is a generally handy import statement for most any program.

For starters, try creating a program that extends the JFrame class. The constructor should call the parent constructor, size the window and show it (as a rule, the setVisible(true) command should be the last statement in a JFrame constructor). You might also draw a few items in the window. Remember, you can't draw within the constructor. This is a good chance to try out a useful feature of BlueJ. First, you can make a new method to do some drawing. Then once you have compiled your program, you can create a new object as you normally do which ought to bring up your JFrame. Now if you control-click the object (in the project window, it should be a red rectangle at the bottom) you can call individual methods for the object, including your drawing method. This is a nice way to test whether individual methods within a class work the way you want them to.

Responding to the Mouse

You may already have some experience dealing with the mouse from 101. In this lab we will do more of the same, but again will do this in "real" Java.

In many cases Java provides unimplemented class definitions in something called an interface. We talked about these in class, and the rest of this section is mostly a rehash of that discussion. Remember that the details of the implementation are left to the programmer (you). The reason this is useful is that the system then knows how to interact with your program even though you are writing something new. For example, it knows how to let your program know about Mouse events.

The first kind of interface that we are going to deal with is a MouseListener. To use this interface we use the keyword implements. For example:

public class MyClass extends JFrame implements MouseListener 

MouseListener is defined in java.awt.event, so you'll need an import like this:

import java.awt.event.*;

The MouseListener interface calls for five methods that you will have to include in your class. The signatures of these are as follows:

mouseClicked(MouseEvent e)
mousePressed(MouseEvent e)
mouseReleased(MouseEvent e)
mouseEntered(MouseEvent e)
mouseExited(MouseEvent e)

These function basically like the methods you have seen with one obvious exception. Instead of passing a Location to your program they pass a MouseEvent. This actually contains much more information than a simple location, but for our purposes we'll just worry about getting the location out. Happily, like Locations, MouseEvents have getX()and getY() methods.

In this lab you'll need three of the methods. The mousePressed event will be used to initiate drawing. The mouseReleased event will be used to terminate a drawing command. The mouseClicked will be used to reset the drawing window. Essentially the user is to use the mouse to draw lines, ovals, and rectangles. The mousePressed command determines the starting point of the object to be drawn, while the mouseReleased command determines its final point (therefore its height and width if appropriate). Despite the fact that you only need three of these methods you still need to define all five. The implements keyword says that you will do so.

Finally, it is not enough to implement the MouseListener. You must also make sure that you add it to the JFrame (this seems strange since it is being defined as part of the JFrame). Basically, just add the following line to your constructor

addMouseListener(this);

You've added your class to itself!

We've got Buttons

One of the issues we have to face in this program is selecting what it is we want to draw. We'll give the user choices by using JButtons. JButtons are another kind of Java component and as such you interact with them through the use of a listener. In this program we are going to attach our buttons to a JPanel, and then attach that JPanel to our JFrame. This might seem a bit excessive, but it is helpful to ensure that our buttons are placed nicely.

JButtons can be declared like any other variable. Once declared, the trick is to attach them to something (like a JPanel) and to define a listener, such that they respond to being pressed. To do this you'll need code very much like the following:

private JButton rectangle, line;
JPanel buttonArray;

rectangle = new JButton("Rectangle");
line = new JButton("Line");
buttonArray = new JPanel();

buttonArray.add(rectangle);
buttonArray.add(line);

rectangle.addActionListener(new ActionListener() {
   public void actionPerformed(ActionEvent e) {
      drawRectangles();
   }
   });
line.addActionListener(new ActionListener() {
   public void actionPerformed(ActionEvent e) {
      drawLines();
   }
   });
getContentPane().add("South", buttonArray);

In the example two buttons would be created. When the user pressed the "Rectangle" button, a method called drawRectangles() would be called (it would be up to you to define this method). The last line of the code adds the JPanel to the JFrame and tells Java that the panel should appear at the bottom of the frame (you could also use the other directions). This is our first example of the Java layout manager in action.

For this program you should define a number of buttons in order to choose the kind of object to draw as well as its color. So, for example, the drawLines() method might set a global variable objectType to a value corresponding to a framedRect. Note: the "draw" buttons shouldn't actually draw anything, they should set some variables such that the next press and drag combination do the drawing (e.g. to draw a rectangle I would first press the Rectangle button and then I would press the mouse at one corner and release it at the far corner).

For some of the buttons you will need to be able to retrieve and set the current graphics context. An example would be

Graphics g = getGraphics(); 
g.setColor(Color.red); 

Putting it together

When it is complete, your program should work as an interactive drawing tool. The user should be able to press buttons to select what they want to draw, or the current drawing color. Clicking the mouse should reset the drawing window (you can do this with the clearRect(int x, int y, int width, int height) graphics method). A common problem that crops up is clearing where the buttons are drawn, there are a number of solutions to this, perhaps the best is to repaint() them. E.g. if I have a Button (or JPanel which includes lots of Buttons) called "x" I could call x.repaint() to redraw the Button (or JPanel).

Your program should have a nice, easy to use interface. Experiment with different layouts of the buttons, for example. Keep in mind that you can use multiple JPanels, and that even a single JPanel can have its own layout.

The most frequent error in a program like this comes from not worrying about what can go wrong. To really think like a computer scientist you need to consider all of the things that can go wrong in a program. For this program a common thing that can go wrong is that the user can specify the rectangle "backwards". E.g. the bottom right corner first, then the upper left second. Be sure that all possible pairs of corners work.

Possible extensions Have a mode where the user can do freehand drawing. Have a mode where the user can select an item that was drawn and choose a new color for it, or move it to a new location. Have an "undo" button that removes the last thing drawn (the best way to do this is to use an array to store what you are drawing). Some of these extensions would require more with the mouse, namely a MouseMotionListener. To do this (or simply to get help understanding other parts of this assignment) it might be helpful to look at the Sun Java pages Here.

What to hand in

Email me the file(s) as attachments, and hand in hard copy. You need not do any sort of lab report for this lab, but as with every lab, you do need to hand in a signed statement that you followed course rules.

Your programs should be well organized and commented. You should use constants where appropriate.