Note: Two programs are provided with this article:
MS Paint With Rubber Band Line
"Rubber band objects" can best be described if we refer to the common experiences that we've had using familiar graphics programs.
Take Microsoft Paint, for instance. Open MS Paint, which automatically starts a new paint document. Click on the line tool. Using the line tool, left-click anywhere within the drawing area. While holding the left mouse button down, move the pointer around the drawing area. (See the image on the right.)
Notice how the line that you are creating acts like a rubber band. It's anchored at the point where the left-button-down event occured. As we move the pointer around the drawing area, the line becomes a rubber band, stretching and shrinking between the anchor point and the current position of the mouse pointer. When we release the mouse left button, the line becomes fixed in place.
Even though that example will get everybody acquainted to the rubber band concept, let me add the following wordy definition for "rubber band objects".
A "rubber band object" is any object created within a graphics program which can be defined in relation to two or more points on a plane. Typically, all of the defining points except one are anchored (umoveable). The last point will continually move with the motion of the pointer or cursor. The rubber band object will continually scale itself between the anchored points, and the moveable point, giving it a rubber band character.
Most of the time, rubber band objects are defined by only two points. The first one is anchored in the graphics area, while the second point is continually moved by the user until the user completes her drawing operation.
If we only look at MS Paint, we can see three objects that can be made to behave like rubber bands: lines, rectangles and circles. If you look at good CAD programs, however, you may fine 50 to 100 objects that, when drawn, will stretch and scale themselves between the anchor point and the moveable point. In addition to that, CAD programs will often provide entire libraries of pre-defined shapes (an architectural library, for example), with each object being stretchable and scalable.
The reality is this: all the primitive objects (lines, circles, rectangles, arcs) can be given a rubber band character during drawing. Additionally, any complex object made from these primitive objects can also! So, both the number and type of objects that can be rendered in this manner is infinite.
I'm sure there are several different ways to create rubber band objects in Liberty BASIC. Regardless of the number of methods, though, there seems to be at least two programming challenges that all the various methods must meet.
The first challenge is the precise handling of mouse events, such as left button clicks, right button clicks, and mouse movement. The second challenge is the undrawing of the object at the old pointer location followed by the rapid redrawing of the object at the new mouse location. If the programmer can handle both of these challenges, then she can create rubber band objects.
Why is the handling of mouse events a challenge? Because the same mouse event--a left-button click for instance--may trigger different operations at different times.
Here's an example. Say that you are developing a program that draws lines, and you intend for your lines to behave as rubber band objects. A left-mouse click may do any of the three things listed below:
The FoxCircle Demo Application
So, the program must be coded to understand the context in which a left-mouse click is generated.
Below is shown part of the code from the FoxCircle demo which handles the left-mouse click event. Note the use of two "flags", FoxIsEnabled and FoxFirstClickEstablished, to track the left-mouse clicks.
[Left.Button.Clicked] if (FoxIsEnabled = 1) then if (FoxFirstClickEstablished = 0) then FoxFirstClickEstablished = 1 FoxCornerX = MouseX FoxCornerY = MouseY OldMouseX = MouseX + 0.4 OldMouseY = MouseY + 0.4 else FoxFirstClickEstablished = 0 end if end if
The first thing to notice about the handler is that no values are set unless the Fox drawing routine is enabled. Until fox-drawing is enabled, a left-mouse click event will create no fox images in the graphic box.
Second, if fox-drawing is enabled, the handler determines if a left-mouse click has already occured by evaluating the FoxFirstClickEstablished flag. If prior to entry of the handler that flag is currently zero, then the user intends for the left-mouse click to be the anchor point for the fox. So, the FoxFirstClickEstablished flag will be set to one. Unless this flag holds a value of 1, no rubber banding will be authorized within the mousemove handler, as you will see later.
After the FoxFirstClickEstablished flag is set to one, the handler will capture the coordinates of the anchor point of the fox to be drawn. This step is critical, because every single undraw and redraw of the fox is connected to this anchor point. The two coordinates of the anchor point have been given the variable names FoxCornerX and FoxCornerY.
On the other hand, if the FoxFirstClickEstablished flag is already set to 1 upon entry of the handler, that means that fox-drawing is already active, and a left-mouse click indicates the user's intent for the drawing operation to finish. So, the FoxFirstClickEstablished flag will be set to zero thereby stopping the rubber banding operations until the next left-mouse click begins the process again.
Now that left-mouse clicks are properly handled, here is what is happening over at the mousemove handler:
[MouseChange1] If ((MouseX >= 10) and (MouseX <= 330) and (MouseY >= 10) and (MouseY <= 330)) then cursor CROSSHAIR else cursor ARROW end if If ((FoxIsEnabled = 1) and (FoxFirstClickEstablished = 1)) then QQQ = DrawTheFox(FoxCornerX, FoxCornerY, OldMouseX, OldMouseY) QQQ = DrawTheFox(FoxCornerX, FoxCornerY, MouseX, MouseY) OldMouseX = MouseX OldMouseY = MouseY end if
Clearly, the first thing that I've done is to change the appearance of the cursor in the interior of the graphic box. While not essential, it is nice to have a CROSSHAIR in the graphic box to allow the user to more easily identify a drawing point.
Next, notice that no drawing takes place at all unless and until both the FoxIsEnabled flag and the FoxFirstClickEstablished flag are both set to one. If both flags are set to 1, then drawing the fox occurs twice. In the first draw, the fox is actually "undrawn" between the anchor point and the "old" mouse location. In the second draw, the fox is drawn anew between the anchor point and the current mouse location.
Ahhh, the perfect seguey to the second challenge...
----------------------------------------- | SIDEBAR: What's the deal with the | | use of functions to draw graphics? | ----------------------------------------- | If you look at the code excerpts | | above, or study the source code in | | FoxCircle and The Cloud Tool, you | | will notice that I am using Functions | | to draw graphic objects. Typically, | | Liberty BASIC Functions are used to | | return values, not draw. | | | | I have a good reason for this: I'm | | lazy. | | | | Actually, about 18 months ago, I | | needed a routine which would draw | | graphics after receiving 6 to 8 | | arguements. The conventional LB | | tool to use for this would be the | | Libert BASIC SUB. However, I had | | never used a SUB, and found that | | LB Functions would also do the job. | | | | Since Functions have not let me | | down yet, I continue to use them for | | graphics from time to time. | | | | But kids, don't try this at home. | -----------------------------------------
Undrawing the object at the old mouse location, followed by redrawing at the new, is the lesser of the two challenges in my opinion. The reason for the relative ease is the XOR rule: if "rule XOR" is in effect during drawing, any object redrawn exactly on top of itself will disappear. This capability makes "undrawing" the object at the "old" pointer location quite easy, provided that the programmer carefully tracks the old pointer location.
So how, and where, is the rule XOR command issued. In FoxCircle, I happen to place the command in the very same function where the graphics are drawn. This is inefficient, however, because Liberty BASIC will execute the command over and over unnecessarily. If your program will only use the XOR rule and no other drawing rules, then you only need to issue the command once, preferrably shortly after your main window is opened.
The XOR rule is a command sent to a GRAPHICBOX control. Here is what the code line looks like in FoxCircle:
print #FoxCirc.GBox1, "rule XOR"
As indicated earlier, the rubber band effect is created by "undrawing" the object between it's anchor point and the old cursor location, and rapidly "redrawing" the object between the anchor point and the new cursor location. So, tracking the cursor is key to creating a successful rubber band object.
Liberty BASIC makes tracking the GraphicBox coordinates of the cursor easy. Carl Gundel has reserved the variable names MouseX and MouseY as holders of the current cursor coordinates in the active GraphicBox. The values of MouseX and MouseY are continually updated by the system without any additional coding provided by the user.
Of course, with every movement of the cursor, you not only need to know the current location of the mouse, but the last location of the mouse, too. This is because a rubber band object needs to be "undrawn" from its last location before it can be "redrawn" at it's current location.
Logically, the "current" location of the mouse converts into the "old" location of the mouse during the next cycle. So, all that needs to be done to capture the "old" location of the mouse is the following:
OldMouseX = MouseX OldMouseY = MouseY
Then, the next time through the cycle, the last position of the mouse has been preserved in the variables OldMouseX and OldMouseY, while MouseX and MouseY will take on new values with the movement of the cursor.
(As an aside, my experience is that MouseX and MouseY are "case sensitive", like user-defined variables. Perhaps anyone with a different experience can speak up about it.)
In the first challenge, we discussed the importance of handling mouse events correctly. In the second challenge so far, we've discussed the importance of "rule XOR", and how to track the old and new locations of the cursor. Now it's time to discuss how you can efficiently draw the rubber band object with each new movement of the mouse.
To begin that discussion, recall a statement that I wrote in the first section of this article:
"Most of the time, rubber band objects are defined by only two points. The first one is anchored in the graphics area, while the second point is continually moved by the user until the user completes her drawing operation."
Since a rubber band object can be completely defined by referencing only two points in the GraphicBox, and since each point in the GraphicBox has two coordinates, then the programmer should be able to write an object-drawing sub or function which only needs four arguements: the X and Y coordinates of the anchor point, and the X and Y coordinates of the moving point. Indeed, that is what you see in the code shown earlier and repeated below:
QQQ = DrawTheFox(FoxCornerX, FoxCornerY, OldMouseX, OldMouseY) QQQ = DrawTheFox(FoxCornerX, FoxCornerY, MouseX, MouseY)
The SUBs or Functions themselves might be quite simple, as in the case when the rubber band object is a line. On other occasions, the SUB or Function might be complex. As an example of the latter, the DrawTheFox() function requires over 200 lines by itself, counting blank lines and comments.
The Cloud Tool Application
Regardless of the complexity of the SUB or Function however, calling the drawing routines should only require four arguements, making the operation very simple and efficient in the mousemove handler.
Feel free to download and unzip the two demo programs included with this article.
FoxCircle allows you to draw, well, foxes and circles. I chose these two objects for this micro-application to demonstrate that both simple objects (circles) and complex objects (foxes) lend themselves to be rendered as "rubber band objects". In FoxCircle, a mouse-left click is used both to start drawing an object and to finish drawing the object
More developed than Fox Circle, The Cloud Tool allows the user to string together systems of rubber band arcs in order to make clouds. I built that program in order to imitate the cloud-drawing tools that one finds in the nicer CAD programs, such as AutoCad. To start drawing a cloud, left-click the mouse. Each new billow of a cloud is also formed by left-clicking the mouse. To complete the cloud, right-click the mouse.
The Cloud Tool comes with loading, saving and printing functions, as well as an options dialog box.