Main Page

Previous Next

Drawing Using the Mouse

We've drawn shapes so far using data internal to the program. In our Sketcher program we want to be able to draw a shape using the mouse in the view, and then store the finished shape in the model. We want the process to be as natural as possible, so we'll implement a mechanism that allows you to draw by pressing the left mouse button (more accurately, button 1) and dragging the cursor to draw the selected type of shape. So for a line, the point where you depress the mouse button will be the start point for the line, and the point where you release the button will be the end point.

Click To expand

As you drag the mouse with the button down, we'll display the line as it looks at that point. Thus the line will be displayed dynamically all the time the mouse cursor is being dragged and the left button remains pressed. This process is called rubber-banding.

We can use essentially the same process of pressing the mouse button and dragging the cursor for all four of our shapes. Thus two points will define each shape – the cursor position where the mouse button is pressed, and the cursor position where the mouse button is released, (plus the color for the shape, of course). This implies that our shape constructors will all have three parameters, the two points and the color. Let's look at how we handle mouse events to make this work.

Handling Mouse Events

Because all the drawing operations for a sketch will be accomplished using the mouse, we must implement the process for creating elements within the methods that will handle the mouse events. The mouse events we're interested in will originate in the SketchView object because that's where we'll be drawing shapes. We will make the view responsible for handling all its own events, which will include events that occur in the drawing process as well as interactions with existing shapes.

Drawing a shape, such as a line, interactively will involve us in handling three different kinds of mouse event. Let's summarize what they are, and what we need to do when they occur:



Left Button (Button 1) pressed

Save the cursor position somewhere as the starting point for the line. We'll store this in a data member of the inner class to SketchView that we'll create to define listeners for mouse events.

Mouse dragged

Save the current cursor position somewhere as the end point for the line. Erase any previously drawn temporary line, and create a new temporary line from the starting point that was saved initially. Draw the new temporary line.

Left Button (Button 1) released

If there's a reference to a temporary line stored, add it to the sketch model, and redraw it.

You'll remember from the previous chapter that there are two mouse listener interfaces: MouseListener which has methods for handling events that occur when the mouse buttons are pressed or released and MouseMotionListener which has methods for handling events that arise when the mouse is moved. You will also recall that the MouseInputAdapter class implements both, and since we need to implement methods from both interfaces, we'll add an inner class to the SketchView class that extends the MouseInputAdapter class.

Since there's quite a lot of code involved in this, we will first define the bare bones of the class to handle mouse events, and then build in the detail until it does what we want.

Try It Out— Implementing a Mouse Listener

Add the following class outline as an inner class to SketchView:

import javax.swing.JComponent;
import java.util.*;              // For Observable, Observer, Iterator                  
import java.awt.*;               // For Graphics, Graphics2D, Point
import java.awt.event.MouseEvent;    
import javax.swing.event.MouseInputAdapter;    

class SketchView extends JComponent implements Observer {
  // Rest of the class as before

  class MouseHandler extends MouseInputAdapter {
    public void mousePressed(MouseEvent e)  {
      // Code to handle mouse button press...

    public void mouseDragged(MouseEvent e) {
      // Code to handle the mouse being dragged...

    public void mouseReleased(MouseEvent e) {
      // Code to handle the mouse button being release...

    private Point start;                     // Stores cursor position on press
    private Point last;                      // Stores cursor position on drag
    private Element tempElement;             // Stores a temporary element

We have implemented the three methods that we will need to create an element. The mousePressed() method will store the position of the cursor in the start member of the MouseHandler class, so it will be available to the mouseDragged() method that will be called repeatedly when you drag the mouse cursor with the button pressed. The mouseDragged() method will create an element using the current cursor position and the position saved in start, and store a reference to it in the tempElement member of the class. The last member will be used to store the cursor position when mouseDragged() is called. Both start and last are of type Point since this is the type that we will get for the cursor position, but remember that Point is a subclass of Point2D, so you can always cast a Point reference to Point2D when necessary. The process ends when you release the mouse button, causing the mouseReleased() method to be called.

An object of type MouseHandler will be the listener for mouse events for the view object, so we should put this in place in the SketchView constructor. Add the following code at the end of the existing code:

  public SketchView(Sketcher theApp) {
    this.theApp = theApp;
    MouseHandler handler = new MouseHandler();        // create the mouse listener
    addMouseListener(handler);                        // Listen for button events
    addMouseMotionListener(handler);                  // Listen for motion events

We call the addMouseListener()and addMotionListener() methods and pass the same listener object because our listener class deals with both. Both these methods are inherited from the Component class that also defines an addMouseWheelListener() method for when you want to handle mouse wheel events.

Let's go for the detail of the MouseHandler class now, starting with the mousePressed() method.

Handling Mouse Button Press Events

The first thing we will need to do is find out which button is pressed. It is generally a good idea to make mouse button operations specific to a particular button. That way you avoid potential confusion when you extend the code to support more functionality. This is very easy to do. The getButton() method for the MouseEvent object that is passed to a handler method returns a value of type int that indicates which of the three supported buttons changed state. It can return one of four constant values that are defined in the MouseEvent class, BUTTON1, BUTTON2, BUTTON3, or NOBUTTON, the last constant being the return value when no button has changed state in the current mouse event. On a two-button mouse or a wheel mouse, button 1 for a right-handed user is the left button and button 2 is the right button. Of course, these are reversed if you have a left-handed mouse setup. Button 3 is the middle button when there is one. We can detect when button1 is pressed by using the following code in the mousePressed() method:

public void mousePressed(MouseEvent e) {
  if(e.getButton() == MouseEvent.BUTTON1)   
    // Code to handle button 1 press...

A MouseEvent object records the current cursor position, and you can get a Point reference to it by calling the getPoint()method. For example:

public void mousePressed(MouseEvent e) {
  start = e.getPoint();                   // Save the cursor position in start
  if(e.getButton() == MouseEvent.BUTTON1) {
    // Rest of the code to handle button 1 press...

This saves the current cursor position in the start field of our MouseHandler object. We save it before the if statement as we are likely to want the current cursor position available whichever button was pressed.

As well as saving the cursor position, our implementation of mousePressed() must set things up to enable the mouseDragged()method to create an element and display it. One thing the mouseDragged() method needs to know is whether button 1 is down or not. The getButton() method won't do this in this case since it records which button changed state in the event, not which button is down, and the button state won't change as a consequence of a mouse dragged event. We can store the state of button one when the mousePressed() method is called, though. Then it will be available to the mouseDragged() method. First, we need to add a suitable field to the MouseHandler class:

private boolean button1Down = false;          // Flag for button 1 state

Now we can store the state when the mousePressed() method executes:

    public void mousePressed(MouseEvent e) {
      start = e.getPoint();                 // Save the cursor position in start
      if(button1Down = (e.getButton() == MouseEvent.BUTTON1)) {
        // Rest of the code to handle button 1 press...

The mouseDragged() method is going to be called very frequently, and to implement rubber-banding of the element each time, the redrawing of the element needs to be very fast. We don't want to have the whole view redrawn each time, as this will carry a lot of overhead. We need a different approach.

Using XOR Mode

One way to do this is to draw in XOR mode. You set XOR mode by calling the setXORMode() method for a graphics context, and passing a color to it – usually the background color. In this mode the pixels are not written directly to the screen. The color in which you are drawing is combined with the color of the pixel currently displayed together with a third color that you specify, by exclusive ORing them together, and the resultant pixel color is written to the screen. The third color is usually set to be the background color, so the color of the pixel that is written is the result of the following operation:

resultant_Color = foreground_color^background_color^current_color

The effect of this is to flip between the drawing color and the background color. The first time you draw a shape, the result will be in the color you are drawing with, except for overlaps with other shapes, since they won't be in the background color. When you draw the same shape a second time the result will be the background color so the shape will disappear. Drawing a third time will make it reappear. We saw how this works back in Chapter 2 when we were discussing the bitwise exclusive OR operation.

Based on what we have said, we can implement the mousePressed() method for the MouseHander class like this:

public void mousePressed(MouseEvent e) {
  start = e.getPoint();                   // Save the cursor position in start
  if(button1Down = (e.getButton() == MouseEvent.BUTTON1)) {
    g2D = (Graphics2D)getGraphics();                 // Get graphics context
    g2D.setXORMode(getBackground());                 // Set XOR mode
    g2D.setPaint(theApp.getWindow().getElementColor());     // Set color

If button 1 was pressed, we obtain a graphics context for the view, so we must add another member to the MouseHandler class to store this:

    private Graphics2D g2D = null;                   // Temporary graphics context

We use the object, g2D, to set XOR mode, as we will use this mode in the mouseDragged() method to erase a previously drawn shape without reconstructing the whole sketch. The last thing that is done here is to retrieve the current drawing color that is recorded in the SketchFrame object. You will remember that this is set when you select a menu item or a toolbar button. We use theApp object stored in the view to get the SketchFrame object, and then call its getElementColor() member to retrieve the color. This method doesn't exist in SketchFrame, but it's not difficult. Add the following method to the SketchFrame class definition:

public Color getElementColor() { 
  return elementColor; 

With the button press code in place, we can have a go at implementing mouseDragged().

Handling Mouse Dragging Events

We can obtain the cursor position in the mouseDragged() method in the same way as for the mousePressed()method, which is by calling getPoint() for the event object, so we could write:

last = e.getPoint();                    // Get cursor position

But then we only want to handle drag events for button 1, so we will make this conditional upon the button1Down field having the value true. When mouseDragged() is called for the first time, we won't have created an element, so we can just create one from the points stored in start and last, and then draw it using the graphics context saved by the mousePressed() method. The mouseDragged() method will be called lots of times while you drag the mouse though, and for every occasion other than the first, we must take care to redraw the old element before creating the new one. Since we are in XOR mode, drawing the element a second time will draw it in the background color, so it will disappear. Here's how we can do all that:

public void mouseDragged(MouseEvent e) {
  last = e.getPoint();                               // Save cursor position

  if(button1Down) {
    if(tempElement == null) {                       // Is there an element?
      tempElement = createElement(start, last);     // No, so create one
    } else {
      g2D.draw(tempElement.getShape());             // Yes – draw to erase it
      tempElement.modify(start, last);              // Now modify it
    g2D.draw(tempElement.getShape());               // and draw it

If button 1 is pressed, button1Down will be true so we are interested. We first check for an existing element by comparing the reference in tempElement with null. If there isn't one we create an element of the current type by calling a method, createElement(), that we will add to the SketchView class in a moment. We save a reference to the element that is created in the tempElement member of the listener object.

If tempElement is not null then an element already exists so we modify the existing element to incorporate the latest cursor position by calling a method modify() for the element object. We will need to add an implementation of this method for each element type. Finally we draw the latest version of the element referenced by tempElement. Since we expect to call the modify() method for an element polymorphically, we should add it to the base class, Element. It will be abstract in the Element class so add the following declaration to the class definition:

public abstract void modify(Point start, Point last);

Since we reference the Point class here we need an import statement for it in the file:

import java.awt.Point;

We can implement createElement() as a private member of the MouseHandler class, since it's not needed anywhere else. The parameters for the method are just two points that will be used to define each element. Here's the code:

private Element createElement(Point start, Point end) {
  switch(theApp.getWindow().getElementType()) {
    case LINE:
       return new Element.Line(start, end,
    case RECTANGLE:
       return new Element.Rectangle(start, end,
    case CIRCLE:
       return new Element.Circle(start, end, 
     case CURVE:
     return new Element.Curve(start, end,
      assert false;                     // We should never get to here
  return null;

Since we refer to the constants identifying element types here, we must make the SketchView class implement the Constants interface, so modify the class to do that now. The first line of the definition will be:

class SketchView extends JComponent
                 implements Observer, Constants

The createElement() method returns a reference to a shape as type Element. We determine the type of shape to create by retrieving the element type ID stored in the SketchFrame class by the menu item listeners that we put together in the previous chapter. The getElementType()method isn't there in the SketchFrame class yet, but you can add it now as:

public int getElementType() { 
  return elementType; 

The switch statement in createElement() selects the constructor to be called, and as you see, they are all essentially of the same form. If we fall through the switch with an ID that we haven't provided for, we return null. Of course, none of these shape class constructors exists in Sketcher yet. So if you want to try compiling the code we have so far, you will need to comment out each of the return statements. The constructor calls imply that all our shape classes are inner classes to the Element class. We will see how to implement these very soon. But first, let's add the next piece of mouse event handling we need – handling button release events.

Handling Button Release Events

When the mouse button is released, we will have created an element. In this case all we need to do is to add the element referred to by the tempElement member of the MouseHandler class to the SketchModel object that represents the sketch. One thing we need to consider though. Someone might click the mouse button without dragging it. In this case there won't be an element to store, so we just clean up the data members of the MouseHandler object.

public void mouseReleased(MouseEvent e) {
  if(button1Down = (e.getButton()==MouseEvent.BUTTON1)) {
    button1Down = false;                    // Reset the button 1 flag
    if(tempElement != null) {
      theApp.getModel().add(tempElement);   // Add element to the model
      tempElement = null;                   // No temporary now stored
    if(g2D != null) {                       // If there's a graphics context
      g2D.dispose();                        // ...release the resource
      g2D = null;                           // Set field to null
    start = last = null;                    // Remove the points

When the button1 is released it will change state so we can use the getButton() method here. Of course, once button 1 is released we need to reset our flag, button1Down. If there is a reference stored in tempElement we add it to the model by calling the add() method that we defined for it and set tempElement back to null. It is important to set tempElement back to null here. Failing to do that would result in the old element reference being added to the model when you click the mouse button.

When we add the new element to the model, the view will be notified as an observer, so the update() method in the view will be called. We can implement the update() method in the SketchView class like this:

public void update(Observable o, Object rectangle) {
  if(rectangle == null)

If rectangle is not null, then we have a reference to a Rectangle object that was provided by the notifyObservers() method call in the add() method for the SketchModel object. This rectangle is the area occupied by the new element, so we pass this to the repaint() method for the view to add just this area to the area to be redrawn on the next call of the paint() method. If rectangle is null, we call the version of repaint() that has no parameter to redraw the whole view. Another important operation here is calling the dispose() method for the g2D object. Every graphics context makes use of finite system resources. If you use a lot of graphics context objects and you don't release the resources they use, your program will consume more and more resources. Under Windows for instance you may eventually run out, your computer will stop working, and you'll have to reboot. When you call dispose() for a graphics context object it can merely no longer be used, so we set g2D back to null to be on the safe side.


A reminder about a potential error in using adapter classes – be sure to spell the method names correctly. If you don't, your method won't get called, but the base class member will. The base class method does nothing so your code won't work as you expect. There will be no warning or error messages about this because your code will be perfectly legal – though quite wrong. You will simply have added an additional and quite useless method to those defined in the adapter class.

We have implemented all three methods that we need to draw shapes. We could try it out if only we had a shape to draw.

Previous Next
JavaScript Editor Java Tutorials Free JavaScript Editor