REALbasic University Resources:

RBU: Glossary Defines common REALbasic programming terms
  Archives Previously published columns
Translations: Dutch Courtesy of Floris van Sandwijk
  Japanese Courtesy of Kazuo Ishizuka
  Chinese Courtesy of Dong Li
  RBU Translation Guide Information on Translating RBU into other languages
Books: Matt's Book (2nd Edition!) Ideal for experienced programmers
  Erick's Book Best for beginning programmers
Websites: Mother Ship The publisher of REALbasic
  RB Webring Links to hundreds of REALbasic websites
  RESExcellence Another REALbasic programming column
  REALbasic Developer Magazine The premiere source for REALbasic instruction.

REALbasic University is Sponsored by

Make your Mac do what YOU want it to. Create games, utilities, cool Mac OS X tricks. Download REALbasic now and create your own software.


Print This Article

REALbasic University: Column 088

OOP University: Part Twelve

In our previous lesson, we made it so SuperDraw, our object-oriented drawing program, supported selecting and deselecting drawing elements. Today we'll continue to enhance the program, making it so we can move and resize them!

Enabling Movement

To add the two features we plan to add today -- object dragging and object resizing -- we'll need to check for two different types of situations. For instance, object resizing only happens when the user is dragging a corner handle. If the user's dragging in inside an element, they're just moving it.

Since we want both features, let's plan ahead and code for both at the same time, though initially we won't add the resize functionality. To do this, let's add a method to shapeClass.

Remember last time we added a withinMe method to shapeClass our objects will tell us if the click is inside them? Well, this time we're going to do the same thing again, except this method will check to see if the click is inside a corner handle.

Open shapeClass and add a new method (Edit menu, "New Method...") like this:

  
function withinHandle(x as integer, y as integer) as boolean
dim w, h, w1, h1 as integer

theHandle = -1 // None
w = left - 4
h = top - 4
w1 = left + width + 4
h1 = top + height + 4
draggingHandle = true
if x >= w and x <= left and y >= h and y <= top then
theHandle = 0 // upper left
return true
end if
if x >= w1 - 4 and x <= w1 and y >= h and y <= top then
theHandle = 1 // upper right
return true
end if
if x >= w1 - 4 and x <= w1 and y >= h1 - 4 and y <= h1 then
theHandle = 2 // lower right
return true
end if
if x >= w and x <= left and y >= h1 - 3 and y <= h1 then
theHandle = 3 // lower left
return true
end if

// not clicking on any handle
draggingHandle = false
end function

The code here is simple enough: we check the area of each corner handle to see if the x and y coordinates passed are within those areas. If so, we return true. We also set a property called theHandle to either 0 through 3 to indicate which corner handle is being dragged, as well as set draggingHandle to true.

In Detail

You might notice that I've written this in a potentially problematic manner: I've hard coded in the sizes of the handles (4 pixels). Ideally this should be a constant. That way if you decided to change the handle size (for whatever reason), you could change the constant and no code would break. The way I've written it, you'd have to change it within several routines.

Unfortunately, REALbasic objects don't support constants (only modules do), so avoiding this kind of problem is a pain. One solution is to add methods that return the value you want -- i.e. a method called handleSize that returns 4 -- but that's a lot of overhead for something as simple as a constant. Perhaps in the future RB will allow classes to contain constants.

While we're here, we better add those two new properties:

  
draggingHandle as boolean
theHandle as integer

Bug Alert

A few sharp-eyed readers may have noticed that for the method withinMe we pass a graphics object in addition to the click coordinates, but for withinHandle we only pass the click point. Why?

Well, that's a oversight. A bug. Not a bug that causes any kind of a problem, but it's extra code. Extra code is not only wasteful, it can cause problems by making the code more difficult to understand. For instance, you'll notice that the passed parameter g isn't even used in the withinMe method!

So let's fix that while we're here. Change the parameters line of withinMe to read simply x as integer, y as integer. Next, open drawCanvasClass and go to the mouseDown event. In the call to withinMe, remove the me.graphics parameter so the corrected line looks like this:

  
if objectList(i).withinMe(x, y) then

Like I said, having this extra parameter won't hurt anything, but there's no point and it's sloppy, so it's best to get rid of it.

Going back to our moving object problem, let's open drawCanvasClass and go to the mouseDrag event. Add in this code:

  
dim i, n as integer

if currentObject.draggingHandle and currentObject.selected then



// elseif determines if x/y has moved
// and it's a drag, not a click
elseif (x <> xStart) or (y <> yStart) then
// Is moving object...

// Calculate difference (amount moved)
xStart = x - xStart
yStart = y - yStart

// Make sure it's selected
currentObject.selected = true

n = objectCount
for i = 1 to n
if objectList(i).selected then
objectList(i).left = objectList(i).left + xStart
objectList(i).top = objectList(i).top + yStart
end if
next // i

// Reset to current mouse point
xStart = x
yStart = y

me.refresh
end if

As you can see, we've left some blank area for what happens if the user drags a handle. We'll deal with that in a minute. Our two choices for dragging are either dragging a handle or dragging a drawing object, so the elseIf line above tells us we're not dragging a handle but moving an object. Actually, since more than one object might be selected, we might be moving multiple objects as part of a group selection!

For moving, the first thing we do is calculate the amount of movement and store this into xStart and yStart. By virtue of the mathematical formula we use to calculate this amount, these values could be negative if moving left and/or up. Then we start a simple for/next loop through all objects, find the selected ones, and move them by that amount (we add xStart and yStart).

Our final step is to reset xStart and yStart to the current mouse position -- this is vital so that next time mouseDrag is called, the correct mouse movement amounts are calculated. We obviously want the amount moved to be from the previous mouse position, not the initial click point. We conclude by redrawing drawCanvasClass, which makes sure the objects are redrawn in their new places.

Go ahead and try the project: it should run and let you move objects around.

Resizing Objects

Reszing our drawing objects sounds complicated, but we've actually done most of the work already!

First, in the mouseDrag event, we need to put in code for what happens during a handle drag (resize). So put this after the if currentObject.draggingHandle and currentObject.selected then line:

  
// Resize based on which handle is being dragged

// Calculate difference
xStart = x - xStart
yStart = y - yStart
select case currentObject.theHandle
case 0 // Upper left
adjustObject(xStart, yStart, -xStart, -yStart)
case 1 // Upper right
adjustObject(0, yStart, xStart, -yStart)
case 2 // Lower right
adjustObject(0, 0, xStart, yStart)
case 3 // Lower left
adjustObject(xStart, 0, -xStart, yStart)
end select

me.refresh
// Reset to current mouse point
xStart = x
yStart = y

Once again, this calculates the movement amount, then passes this info to a new method called adjustObject which actually resizes the object. Because different corner handles drag in different directions, we change what values we pass to adjustObject. This allows the object to grow (or shrink) in the same direction as the drag.

Finally, we refresh and save the current mouse position.

Of course for this to work, we've got to add the adjustObject method. It looks like this:

  
Private sub adjustObject(aLeft as integer, aTop as integer, aWidth as integer, aHeight as integer)
const minSize = 10

currentObject.left = currentObject.left + aLeft
currentObject.top = currentObject.top + aTop
currentObject.width = currentObject.width + aWidth
currentObject.height = currentObject.height + aHeight

// Size is below minimum, so undo what we just did
if currentObject.width < minSize then
currentObject.left = currentObject.left - aLeft
currentObject.width = currentObject.width - aWidth
end if

if currentObject.height < minSize then
currentObject.top = currentObject.top - aTop
currentObject.height = currentObject.height - aHeight
end if
end sub

You'll note it takes four integers as parameters, and it's defined as private. Private means that the method can only be called from within drawCanvasClass. In other words, window1 cannot call adjustObject.

Why set the method to private? Well, that's an important OOP concept. By having some routines private and others public, the object can control what aspects of itself it allows other objects to see. Routines that deal with things only the object needs to modify are best set to private to avoid external routines from messing with stuff that only the object itself should touch. In this case, window1 (or any other object) has no business telling an object to resize -- that's drawCanvasClass' private business.

If you run SuperDraw now, you'll find that handle clicking doesn't seem to work. That's because we didn't add a check within mouseDown for such an event!

Go to drawCanvasClass' mouseDown event and after the n = objectCount line, put in this code:

  
// Is it within a handle?
for i = n downto 1
if objectList(i).selected and objectList(i).withinHandle(x, y) then
// currentObject now points to the clicked on object
currentObject = objectList(i)
xStart = x
yStart = y
return true // No point in continuing loop
end if
next // i

This looks through all the objects. It first checks to see if object is selected -- since there's no point in bothering to check for a handle click on an unselected object -- and then ask the object if the click is within a handle. Remember, if withinHandle returns true, the object's theHandle property is filled with which corner was clicked on. That's how our drag routine knows which handle is being dragged!

If the user is dragging a handle, we set currentObject to the current object in the loop, set xStart and yStart, and exit the mouseDown event (as the comment says, there's no point in continuing).

Guess what? That's it! We're done!

Run SuperDraw and see what you can do. You should be able to select, deselect, move, and resize any of the objects you add to the canvas:

Cool, eh? Note that because of the object-oriented way we programmed this, today we didn't modify any external code (window1 or canvas1) or any of the sub-objects we created a while back (circleClass, rectClass, triangleClass, etc.) Yet our simple modifications worked with all those objects!

That's the power of objects. But you haven't seen anything yet! In our next lesson we're going very quickly and very easily add several brand new object types and you're going to be amazed to see them automatically take on characteristics you didn't even realize they had!

If you would like the complete REALbasic project file for this week's tutorial (including resources), you may download it here.

Next Week

We add new object types to SuperDraw.

REALbasic University Quick Tip

Here's a simple but often overlooked tip. REALbasic's tip window is incredibly handy for showing you the parameters required for by a method. Even better, it will display the parameters for routines that you create yourself!

But notice that what's displayed are the variable names you use in your method's definition. Use description names for parameters in your methods: this way they'll show up in the Tip Window and you'll have a better idea of what that routine needs.

Isn't this much easier to understand than the first tip window?

News

This week REAL Software announced that REALbasic 5 for Macintosh is finally shipping! This is the most significant rewrite of REALbasic ever (it includes a new compiler), and promises a bright future for the product. Some of the new features include better Jaguar (Mac OS X) support, new communication protocols (email, http, etc.), and new language features.

You can read more about REALbasic 5 on the REAL Software website. Upgrades start at $29.95.

If you're curious about the new REALbasic 5 features, the upcoming April/May issue of REALbasic Developer magazine features not one, but two in-depth articles on this significant upgrade.

Letters

This week we've got emails responding to last week's column. First, Georg Christmann writes with a "correction" concerning my "Debugging Tip."

Hi Marc,

Sorry to tell you, but the debugger correctly notifies me that the variable "s" is already in use ;-)

BTW, that is the behaviour I would have expected.

But I still like RBU. And I am a subscriber to RB Developer.

All the best

Georg

You are correct that REALbasic 5 catches this: but REALbasic 4.x does not. This is a good thing to note, as RB5 catches many things that are potential problems and alerts us to them before they can become problems.

I also heard from Lars Jensen, who responded to Douglas Beagley's request for 3D help.

Hi Marc - Since you didn't publish Douglas' address, perhaps you can forward this to him. To see RB3D in action, I would suggest that Douglas go to my web site:

http://ljensen.com

and check out Iron Guest, a reasonably polished 3D version of a board game that my brother and I designed. (Classic, Windows, and X)

He could also could look at SceneBench, which lets the user see and manipulate nearly all properties of RB3D spaces, objects, backgrounds, and lights via direct manipulation with live feedback. It was written precisely to allow very fast and convenient "what ifs" and API exploration. (Classic only)

Both are pre-release versions, and both are free.

Excellent, Lars! Thanks for sharing such a terrific resource!


About the Column
REALbasic University is a weekly instructional column on programming with REALbasic and is brought to you by REALbasic Developer, the magazine for REALbasic programmers.

Each week we answer select reader questions, and we're always open to ideas for future columns. Send your questions to . (Keep your questions simple and specific. General queries like "How do I write my own web browser?" will be neglected.) Your question won't be answered immediately, but will be answered in a future column. (If you don't want your correspondence published, just be sure to indicate that when you write. Otherwise it's fair game.)

About the Author
is an author, philosopher, graphic designer, photographer, film director, soccer fanatic, and programmer (among other things). He writes for MacOpinion, runs his own software company, Stone Table Software, which sells the revolutionary Z-Write word processor, and is Publisher and Editor of REALbasic Developer. He lives in Northern California with his cats, Mischief and Mayhem, and is rapidly running out of free time.

See the REALbasic University Archives


REALbasic University contents ©2001-2004 by Marc Zeedar and REALbasic Developer. All Rights Reserved.

Email This Article - Comment On This Article

.

Reader Specials

Server Racks Online:
Apple Xserve CompatibleServer Racks and Universal Network Racks
42U KVM Switch Solutions:
High-End Mac and Multi-Platform KVM Matrix switching solutions!
Digital Camera Online:
Great prices on Digital Cameras and accessories!
KVM Switches Online:
Great prices on Mac KVM Switches from the leading manufacturers!
LCD Monitors Online:
Great prices on LCD Monitors from the leading manufacturers!
LCD Projectors Online:
Shop online for LCD Projectors from the leading manufacturers!
USB 2.0 Online:
Great prices on USB 2.0 products from the leading manufacturers

Serious Business Software:
Accounting, Sales, Inventory, CRM, Shipping, Payroll & more!

KVM Switch solutions for MACs:
DAXTEN is a KVM switch, KVM extender and monitor splitter specialist for PC, SUN and MAC applications from name brand manufacturers - offices worldwide.

The "Think Different Store: The iPod Accessories Store - iPod cases, iPod mini, iPod photo, speakers, itrip, inMotion, Soundstage and all other iPod accessories

Earn Cash with the ThinkDifferent Store Affiliates Program

Need A Web Site?
Applelinks Web Hosting Starting at 19.95 a Month

iTunes_RGB_9mm

.

iTunes_RGB_9mm

Cool Mac Gear


iPod 1G-2G
iPod 3G
iPod 4G
iPod Mini
PowerBook-iBook
Keyboard Skins
Garageband