|
Home Services People Process More About Us |
Patterns for Drawlets
A primary goal in the design of Drawlets was to make it as clear as possible. Many neglect the power of good code in making the use of a framework clear to the developers who will use it. Documents that contain too much information and are out of date the minute they are released are used to try and make up for poor design within the framework. Our hope was (and still is) that with a minimal amount of instruction, a few examples, and access to the code, developers would be able to get up to speed as quickly as possible and begin using the framework productively. While many have already used Drawlets successfully to some degree, even a minimal amount of documentation has been noticeably lacking.
This document is an attempt to remedy that problem to some extent. It is loosely based on the appendix to a paper written by Ralph Johnson called Documenting Frameworks Using Patterns, in which he documented the original Smalltalk HotDraw framework using patterns as an example to support his paper.
This document takes a minimalist approach, i.e., you only need to read what you want to know. The first sentence or two of each pattern describes what the pattern is succinctly, while the last few sentences of each pattern provide a summary of how to use the pattern. The body of each pattern gives a more in depth idea of the forces involved and provides more elaboration.
There's a path through this document for everyone:
Many programs need to edit twodimensional drawings such as schematic diagrams or blueprints. Sometimes the elements of these drawing can be treated independently, but often the elements have constraints between them. Direct manipulation techniques are usually the best way to edit a drawing. A user will manipulate an element of a drawing by using the mouse to select the element and then operating upon it. There are many ways to operate upon an element. One way is to move a handle that controls an attribute of the element. A second way is to choose a specialized tool from the palette to change the element's attribute. The designer of a drawing editor needs a wide range of options, but must pick the most appropriate ones to keep from confusing the user. Drawlets is a framework for structured drawing editors (i.e. editors for drawings whose elements have constraints on their behavior). The most important interface in Drawlets is Figure, which is the interface of drawing elements. Figures are responsible for rendering, hittesting, and notifying dependents when their appearance changes. Other important interfaces are Drawing (which represents the entire drawing), InputEventHandler (Tool), Handle, and DrawingCanvas. DrawingCanvases created with Drawlets can be part of a larger application.
Each kind of drawing element is an implementer of Figure. Note that there are already implementers of Figure for the simple geometric objects, such as Ellipse, RectangleShape, and Line, as well as TextLabels. Sometimes these classes are suitable to be used directly. Often it is necessary to make an implementer of Figure or to subclass one of its existing implementers. Most Figures will be subclasses of existing geometric Figures like RectangleShape or Line, so they will inherit the visual presentation of their superclass. An implementer of Figure that is not a subclass of any other implementer must define its own visual presentation. Figure is an implementer of Paintable, so it must define paint(). It must also define getBounds(), and translate(), among others. These are a few of the methods necessary to render and move a Figure, and are part of the minimum methods necessary to define a new implementer of Figure. AbstractFigure is an abstract convenience class that defines most of the default Figure behavior, making it easier to create a new Figure. If AbstractFigure is subclassed, the only methods that must be defined are basicTranslate(), getBounds(), and paint().
Another approach to implementing Figure or subclassing AbstractFigure is defining a new inteface which either extends Figure, or is designed to be mixed in with Figure when necessary. This makes sense if an application expects common behavior from various types of Figures.
The size of a Figure and other numeric attributes are best edited with handles. Textual attributes like names, or numeric attributes that must be precise like dates, are best edited by displaying the text as part of the Figure and letting the user edit it. A Figure's getHandles() method returns an array of handles on the Figure. For good examples of a getHandles() method, see LinearShape or AbstractShape.
Tools are often used in some way to change the attributes of the elements of a drawing. For instance, most Drawlets examples have a SelectionTool, which is used to manipulate handles and move Figures around.
Many attributes in Drawlets are defined by DrawingStyle, which is an interface providing the ability to get and set various values. Often attributes can be changed simply by modifying their value in an elements associated DrawingStyle.
Several types of general purpose handles are already included with Drawlets. They provide the ability to do things ranging from resizing Figures to creating Lines. In many instances, it will be enough simply to use those already provided.
If you do need to make a new implementer of Handle, you may either implement all of the methods in the Handle interface, or if appropriate, you can subclass CanvasHandle (or one of its subclasses). If you do subclass CanvasHandle, you only need to define one method, getBounds(), plus the methods corresponding to any events that you want the handle to respond to. Since you can respond to any of the events that the Handle receives, it is possible to make handles with any kind of behavior.
In addition to Drawing and its components, an application using Drawlets will have an object that implements DrawingCanvas. DrawingCanvases are responsible for displaying and allowing the manipulation of Drawings. DrawingCanvases need a GUI specific placeholder to allow them to reside within the application's GUI, due to the fact that DrawingCanvas has been designed to be implementation independent. Also, if desired, an implementer of DrawingCanvas can be written that is itself a GUI placeholder, although the separation is thus lost. An example of a GUI placeholder is DrawingCanvasComponent, which allows SimpleDrawingCanvases to reside within AWT applications. Another example is JDrawingCanvasComponent, which allows it to reside in JFC applications. One important point is that, due to the separation maintained in SimpleDrawingCanvas, it is able to reside in AWT, JFC, etc., without any change except a different GUI placeholder being used.
In most applications it will be desirable to use SimpleDrawingCanvas because it is ready to go. There will be cases, though, in which it is desirable to create a new implementation of DrawingCanvas; for instance, if a grid was desired on the drawing surface. Drawlets is a framework, and it is designed to be customized to fit the needs of each use.
Basically, tools are simply InputEventHandlers that know about their canvas and act on it depending on the events that are generated. The standard tools usually include the selection tool and creation tools for each drawing element that the user will create. There are standard implementations for these tools, but it is also possible to define new implementers of InputEventHandler that act on canvases. Probably the easiest way to do this is to subclass either CanvasTool or ConstructionTool.
It is important to realize that tools can be set up various ways, and one of the most common of these is to provide a palette the user can use to select them. In other words, it is totally at the discretion of the developer how users will be able to access and use tools.
Tools are not to be confused with actions. Many different actions can be defined for manipulating Drawings, DrawingCanvases, and their contents, while tools are specifically for the purpose of transforming user events into actions on the DrawingCanvas or its contents.
The simplest form of a Line is the Line class, which represents a Line with no ability to connect to other Figures. Most of the time it is desirable to have more functionality than this when using Lines, especially the ability to connect in various ways with other Figures. There are several ways of imposing constraints and special behavior on Lines.
The first way of adding functionality to Lines is used in ConnectingLine. This is a subclass of Line and adds the capability to connect to other Figures. It can be forced to always be connected (i.e. if not connected at both ends, it ceases to exist) through a parameter as well.
The second way of adding functionality to Lines is by making them use customized locators. These locators act as constraints on the Lines' position, and by registering a Line to listen to a Figure and then using a RelativeLocator provided by that Figure, changes in the Figure's position are propagated to the Line, which then requests the updated position from the RelativeLocator.
There are also multiple ways of actually adding Lines to a Drawing. Of course a tool can be used, and it can even impose special constraints on the Line when it is constructed. Another method, used in the GraphNode example, is the use of a creation handle, which allows the user to create a Line by dragging it out of a handle. This also imposes an immediate constraint on the creation of the Line, because the beginning point of the Line is attached to the Figure containing the handle to begin with.
Three interfaces define the functionality which Locators provide. First, Locator itself is able to provide its x, y, r and theta; a MovableLocator can additionally manipulate these values; and a RelativeLocator is a Locator whose position is based on some other position. The unified interface of Locator allows implementers to use whatever means they desire to determine the returned position. In the example of FigureRelativePoint, which implements RelativeLocator (which extends Locator), the position returned is based on the position of a Figure to which the FigureRelativePoint is related. FigureRelativePoints are returned by many different Figure implementations when requestConnection() is called, allowing the Figure requesting the connection to update dynamically when the Figure it is connected to moves.
To provide the dynamic updating, Drawlets uses the notion of a LocationListener. Basically, when a Figure (FigureA) decides to depend on another Figure's (FigureB) position, through a RelativeLocator or some other means, FigureA will register itself as a LocationListener with FigureB, and when the location of FigureB changes, it will notify FigureA (and any other LocationListeners). FigureA is then responsible to update itself accordingly, either through asking a FigureRelativePoint for its new coordinates, or some other means. To sum up, if you want a Figure to update dynamically based on the position of a another Figure, have it register itself as a LocationListener.
It is not necessary to use requestConnection() to set up connections between Figures; it can also be done programmatically. A good example are AdornedLines and Arrows. Basically, when an Arrow is added to an AdornedLine, it is registered as a listener of the AdornedLine. When events are propagated to it from its AdornedLine, it updates accordingly.
| Copyright © 2002-03 by RoleModel Software, Inc. All rights reserved. |