Top 10 in 2008
5 popular archives:
All selections are based on page views.
Best of 2008: A developer's list
JW blogger Dustin Marx names his top 10 technology events of 2008. Highlights include updates to Java SE 6, runtime support in OpenLaszlo 4.2, and the clash of the titans that occurred early in the year, when Sun acquired MySQL on the same day that Oracle announced its acquisition of BEA. No two lists are alike and it's not too late: What were your top 10 for 2008?
Also see:
The rubber band can do more than select objects. A GUI may also employ a rubber band to draw shapes or erase an area of the screen. Behind the scenes, a rubber band object may aid in the interactive resizing of a graphical component. Most shocking, and counter to the preconceived notions of the rubber band, this graphical oddity does not have to be rectangular. Yes, a rubber band may dare to be circular!
Fortunately, Java makes things simple. Using the Abstract Windowing Toolkit, or AWT, as well as taking advantage of Java's powerful interfaces, a developer can easily design a reusable rubber band class -- and even extend it from GUI to GUI. When designing any class, we should always look for ways to keep the design flexible. The more flexible a class is, the more we will be able to use it again. And if we've done our job, the class will also allow for easy extensions so that we can add features without having to rewrite major sections of the class. Avoiding unnecessary future work is always a goal to strive for.
Java is prewired to allow for flexible class structures. As we go through the design of the rubber band, we will rely heavily on Java interfaces. Java interfaces lend themselves to flexible and extendable architectures since they free us from rigid class hierarchies. Instead of being forced to inherit from a common ancestor, objects can play together as long as they implement the appropriate interface. Being locked into a particular class hierarchy can make it difficult to plug in new objects and can sometimes destroy an architecture. It is not always convenient or even possible to extend from a common object if the source is not readily available.
Now, let's get to the design.
(To download the complete source code for this article as a jar file, see Resources.)
Before implementing the rubber band, we need to isolate those elements involved in drawing and manipulating it. Roughly, we split the rubber band concept into two elements: the rubber band and the rubber band canvas. The rubber band is the object that draws a band on the canvas in response to events. The rubber band canvas is simply the place where the rubber band listens for events and where it draws itself.
An optimal design will completely abstract the canvas from its rubber band; that is, the canvas shouldn't have any knowledge of how the rubber band works. Likewise, the rubber band should interact with the canvas only in a well-defined but generic manner. A successful design will also allow the canvas to be any object that can display itself. By following these principles, we can design a rubber band that fits easily into any GUI.
In our implementation, the RubberBand class does not extend from AWT's Component or from Java Foundation Classes' JComponent. The rubber band is never autonomous;
instead, it is always associated with a canvas. That fact allows us to implement RubberBand as a lightweight displayable object. Instead of existing as a component that provides its own drawing context and events,
RubberBand draws itself on a graphics object supplied by RubberBandCanvas. Likewise, RubberBand does not generate its own events. Rather, it responds only to events generated by RubberBandCanvas. Now let's examine the RubberBand class:
import java.awt.*;
import java.awt.event.*;
public class RubberBand
{
private RubberBandCanvas canvas;
private Point startPoint;
private Point endPoint;
private boolean eraseSomething = false;
private class MouseHandler extends MouseAdapter
{
public void mousePressed(MouseEvent e)
{
start(e.getPoint()); // anchor the RubberBand
}
public void mouseReleased(MouseEvent e)
{
erase(); // erase the final band
}
}
private class MouseMotionHandler extends MouseMotionAdapter
{
public void mouseDragged(MouseEvent e)
{
erase(); // erase any old bands
stop(e.getPoint()); // set end point
draw(); // draw the new band
}
}
public RubberBand(RubberBandCanvas c)
{
super();
setCanvas(c);
getCanvas().addMouseListener(new MouseHandler());
getCanvas().addMouseMotionListener(new MouseMotionHandler());
}
protected void draw()
{
Graphics g = getCanvas().getGraphics();
if(g != null)
{
try
{
// We always want to draw using XOR mode
// so that we don't need to call redraw
// to erase the band.
g.setXORMode(canvas.getBackground());
drawRubberBand(g);
// We have drawn something, set the flag
// to indicate that there is something to erase.
setEraseSomething(true);
}
finally
{
g.dispose();
}
}
}
protected void drawRubberBand(Graphics g)
{
// The following if/else block determines where to draw the band.
// Based on the anchor point, the band may be drawn in any of
// four quadrants. The if/else determines which quadrant to draw
// the band in.
if((getEndPoint().x > getStartPoint().x) && (getEndPoint().y > getStartPoint().y))
{
g.drawRect(
getStartPoint().x,
getStartPoint().y,
getEndPoint().x-getStartPoint().x,
getEndPoint().y-getStartPoint().y
);
}
else if((getEndPoint().x < getStartPoint().x) && (getEndPoint().y < getStartPoint().y))
{
g.drawRect(
getEndPoint().x,
getEndPoint().y,
getStartPoint().x-getEndPoint().x,
getStartPoint().y-getEndPoint().y
);
}
else if((getEndPoint().x > getStartPoint().x) && (getEndPoint().y < getStartPoint().y))
{
g.drawRect(
getStartPoint().x,
getEndPoint().y,
getEndPoint().x-getStartPoint().x,
getStartPoint().y-getEndPoint().y
);
}
else if((getEndPoint().x < getStartPoint().x) && (getEndPoint().y > getStartPoint().y))
{
g.drawRect(
getEndPoint().x,
getStartPoint().y,
getStartPoint().x-getEndPoint().x,
getEndPoint().y-getStartPoint().y
);
}
}
protected void erase()
{
// We only erase if there is something to erase!
if(getEraseSomething())
{
draw();
setEraseSomething(false);
}
}
protected final RubberBandCanvas getCanvas() { return this.canvas; }
protected final Point getEndPoint()
{
if(this.endPoint == null)
{
setEndPoint(new Point(0,0));
}
return this.endPoint;
}
protected final boolean getEraseSomething() { return this.eraseSomething; }
protected final Point getStartPoint()
{
if(this.startPoint == null)
{
setStartPoint(new Point(0,0));
}
return this.startPoint;
}
protected final void setCanvas(RubberBandCanvas c) { this.canvas = c; }
protected final void setEndPoint(Point newValue) { this.endPoint = newValue; }
protected final void setEraseSomething(boolean newValue)
{
this.eraseSomething = newValue;
}
protected final void setStartPoint(Point newValue) { this.startPoint = newValue; }
protected void start(Point p)
{
// anchor the band
setEndPoint(p);
setStartPoint(p);
}
protected void stop(Point p)
{
// set the end point, but no coordinate should be < 0
if(p.x < 0)
{
p.x = 0;
}
if(p.y < 0)
{
p.y = 0;
}
setEndPoint(p);
}
}
The code above presents the initial implementation of the RubberBand class in its entirety. That implementation works as a baseline for the versions presented in the following sections.