Csci 210 Lab 2: Drawing

(Laura Toma adapted from Eric Chown)

Overview

In this lab you will build an interactive program that you can use to draw simple shapes. You'll use rectangles, ovals and lines 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 the Swing library. Swing builds on a different graphics library in Java called awt. Applications created with awt depend on the platform, while applications created using Swing components have the same feel 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. All classes defined in Swing have an extra capital J in front. 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 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.

Component hierarchy: A GUI consists of components arranged in a hierarchy (one contained in another); for e.g. a window may contain a panel which contains a few buttons and labels, which in turn...etc. The application has a top-level container that holds all the individual components together into a window. Swing provides a few top-level containers (JFrame, JDialog, JApplet). We will be using JFrame.

Events: One of the things we will want to do with Components 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. When a class wants to pay attention to an event it needs to register as an event listener to that event. For e.g., if a class wants to be notified of the mouse events, it needs to register as a mouse listener.

Outcomes:

In this lab we will start to learn how to use and create Components, how to 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, JButtons, JPanels, etc).

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. The simplest way to do this is to make our program extend the JFrame class.

import javax.swing.*
public class MyDrawingProgram extends JFrame 

Our new class will require a constructor (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. You will not use the ObjectDraw library.

In many cases Java provides unimplemented class definitions in 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: since your program promises to implement all mouse functions, the mouse knows which functions to call on your program (though it will not know what exactly your methods do).

The first kind of interface that we are going to deal with is a MouseListener.

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)

All methods required by MouseListener interface have a MouseEvent as a parameter. When the mouse is clicked (and if your object is a listener to the mouse), a MouseEvent is passed to the object. A MouseEvent actually contains much more information than a simple location, but for our purposes we'll just worry about getting the location out. 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 as a mouse listener!

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. 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 rectangleButton, lineButton;
JPanel buttonArray;

rectangleButton = new JButton("Rectangle");
lineButton = new JButton("Line");
buttonArray = new JPanel();

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

//define what happens when pressing the rectangle button
rectangleButton.addActionListener(new ActionListener() {
   public void actionPerformed(ActionEvent e) {
      drawRectangles();
   }
   });
//define what happens when pressing the line button
line.addActionListener(new ActionListener() {
   public void actionPerformed(ActionEvent e) {
      drawLines();
   }
   });

//add the button panel to content panel of the frame
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.

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); 

The paint method and the painting mechanism

There are many ways to get a Java to draw what you want. However, good programming style requires that all the Java drawing be done in a method called paint. This is a method inherited by any java.awt.Component (therefore also by a JFrame).
    public void paint(Graphics g)

This method is called on each component by the system and by the user:

Take a look at Painting in AWT and Swing. You will not understand everything (actually you will understand very little), but try to get a feeling for how the painting mechanism works --- it will save you a lot of trouble later. The key is that Java will invoke the paint method when it's time to paint (look at the section on painting guidelines).

When this method is invoked, the Graphics object parameter is pre-configured with the appropriate state for drawing on this particular component.

In general, programs should avoid placing rendering code at any point where it might be invoked outside the scope of the paint callback. Why? Because such code may be invoked at times when it is not appropriate to paint -- for instance, before the component is visible or has access to a valid Graphics object. It is not recommended that programs invoke paint() directly. Instead, whenever the application needs to trigger painting, it should call .

Take a look at the two versions of Scribble: one does the drawing in paint; one does the drawing in the mouse methods. Both get the job done, but one is better. Scribble1.java | Scribble2.java

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. One way to do this is with the clearRect(int x, int y, int width, int height) graphics method). Anotehr one is to call repaint().

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.

Your program must contain a main method so that I can test it from the console.

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.

Style

Half of the lab grade is for functionality, the other half for style. For max score your program should follow the OOP (object-oriented programming) guidelines and should be well-commented.

Possible extensions

Grading policy

The grading policy for this lab is the following:

Functionality: 60 points

Note: part of functionality is to handle various relative positions for the mouse presses.

Style: 40 points

What to hand in

  1. Email me the Java file as attachment;
  2. Hand in hard copy, and sign on the front page that you followed course rules.