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 087

OOP University: Part Eleven

Last time we started the process of turning SimpleDraw into SuperDraw by completing the significant step of moving our drawing objects inside a subclass of a canvas object. Today we'll add new capabilities to our objects and you'll begin to see how powerful object-oriented programming can be.

Selecting/Deselecting Drawing Elements

First let's rename the file you created last time SuperDraw.rb, since it won't be SimpleDraw much longer.

Now the main capability we're going to add is the ability to select/deselect drawing elements. To do this we'll use our drawCanvasClass object to intercept mouseDown events and then we'll pass those on to our drawing objects. But here's where we start to get clever and use objects the way they're supposed to be used: instead of doing the "is this click inside an object?" calculation within drawCanvasClass, we'll ask the object to tell us if it's been clicked on!

We'll also need to add a drawing routine to our objects that will draw selection handles that will display if an object is selected.

This is somewhat complicated stuff, and we'll be jumping around a bit, so be prepared. One of the disadvantages of this kind of code is that there's no half-way point: you either get this to work or it doesn't. That means you can't test your code until you've got it all written, which makes writing this stuff tricky. But I'll hold your hand. So let's get started!

Drawing Element Objects

Let's begin by focusing on adding behavior to our drawing elements. We have three kinds of drawing objects: circleClass, rectClass, and triangleClass (shapeClass doesn't really count since it doesn't draw anything). We're going to want to ad behavior to these objects. Specifically, the ability to draw themselves selected or not selected, as well as report if they were clicked on or not. Because of the OOP principle of inheritance, however, we really only need to focus on shapeClass, the parent object. Any features we add there will be automatically gained by the subobjects!

First, add selected as boolean as a new property to shapeClass (double-click on it and choose "Add Property..." from the Edit menu). If this property is true, we know the drawing element is selected.

While we're here, let's also a method to toggle this property. Add a new method ("New Method..." from the Edit menu), call it toggleSelection, and give it this code:

  
selected = not selected

Next, add another method, this one drawSelection with parameter g as graphics:

  
sub drawSelection(g as graphics)
const w = 4

if selected then
// Draw selection handles
g.foreColor = rgb(255, 0, 0) // Red
g.fillRect(left - w, top - w, w, w)
g.fillRect(left + width, top - w, w, w)
g.fillRect(left + width, top + height, w, w)
g.fillRect(left - w, top + height, w, w)
g.foreColor = rgb(0, 0, 0) // Black
end if // selected
end sub

Note that this accepts a graphics object as a parameter, then draws a red square in each of the object's four corners. These are the selection handles. When an object is selected, the handles appear, telling the user that the object is selected. (Eventually these handles will be used to allow the user to resize the object as well.) The size of these selection handles is set by the constant w.

Of course this method does us no good unless it's called (executed), so we need to add code to call it. Open the draw method of shapeClass. If you followed the directions in the last lesson, this is pretty much empty, because shapeClass doesn't draw anything.

But now we want it to call drawSelection. Since all the drawing objects will use the same selection handle system, we can put this call here:

  
// ShapeClass doesn't actually draw anything,
// so we pass on drawing to the sub-object
// and draw selection afterward (if necessary)

drawSelection(g)

Now here we run into our first OOP problem. If you remember, we used overriding to have each subobject of shapeClass execute their own draw method. If we put code within shapeClass' draw method, it will never get called because each subobject's draw method is called instead!

The obvious workaround for this is to put the call to drawSelection within the draw methods for each subobject. But that's not very OOP, is it? Not only would we need to add identical code to each of four objects, we'd have to add that code for any future objects we create as well. No, there's a much better way, the OOP way.

Here's what we'll do: we'll add a new event to shapeClass. All of shapeClasses subobjects will inherit that event, and we'll the draw code into that event for each subobject.

Watch. With shapeClass open, go to the Edit menu and choose "New Event..." Give the event the name paint with the parameter g as graphics. Now within shapeClass' draw method, insert the line paint g above the last line. Not counting the comments, the draw method should look like this:

  
paint g
drawSelection(g)

Perfect. Now open circleClass. If you toggle the triangle next to Events, you should see an event called (shockingly) paint. Here's the cool thing: because this is a subclass of shapeClass, which added the event, you can add code to this event!

Go to circleClass' draw method and select all the text. Cut it and paste it into the paint event. It should look like this:

  
sub paint(g as graphics)
g.drawOval left, top, width, height
end sub

Next, delete the draw method from circleClass. (This is an important step! Don't forget it!)

Now just repeat those steps for rectClass and triangleClass. Move the code from their draw methods into their paint events and delete their draw methods.

If you run SuperDraw, you'll see it works just as before. We haven't broken anything! That's because our code calls our object's draw method. Since our subclasses no longer have a draw method, the default draw method for shapeClass is used. The revised draw method for shapeClass not only tells the subclasses to draw themselves, it also draws selection handles (if appropriate). So right now, with no way to tell an object it's selected, the program works just as before.

Have I Been Clicked?

Obviously, our object needs a way to detect if it has been clicked on or not. So let's add a simple method to check for this.

Within shapeClass, add new method (Edit menu, "New Method..."). Name it withinMe with parameters g as graphics, x as integer, y as integer and have it return a boolean. The full routine looks like this:

  
function withinMe(g as graphics, x as integer, y as integer) as boolean
dim w, h as integer

w = left + width
h = top + height
if x >= left and x <= w and y >= top and y <= h then
return true
end if
end function

What does this do? Well, it's passed a horizontal and vertical coordinate, the click point. It simply looks to see if this is inside itself. If both the x and y values are within itself, then it returns true. If not, the default value returned is false.

That's it! You see now we have a way to ask our object, "Is this click inside you?" and it will tell us yes or no!

Clicking An Object

But how do we get the actual click point so we can pass that on to the object? Easy! Our drawCanvasClass object receives mouseDown events from REALbasic, remember? We just need to intercept these and handle them!

Open drawCanvasClass. In the mouseDown event, put this code:

  
dim i, n as integer

currentObject = new shapeClass
n = objectCount

// Is it within an object?
// We scan in reverse so last (top) objects are clicked on first.
for i = n downto 1
if objectList(i).withinMe(me.graphics, x, y) then
// currentObject now points to the clicked on object
currentObject = objectList(i)
currentObject.toggleSelection
currentObject.draw(me.graphics)
xStart = x
yStart = y

// We've found the object, so no point in continuing loop
return true
end if
next // i

// Clicked on no object, so deselect all
for i = n downto 1
objectList(i).selected = false
next // i
me.refresh

I'll explain what this does in a little bit. First, let's see if we can't get this working first. Add another method:

  
function objectCount() as integer
return uBound(objectList)
end function

Put this code in the mouseUp event:

  
me.refreshRect(currentObject.left - 4, currentObject.top - 4, currentObject.width + 8, currentObject.height + 8)

Finally, add these properties to drawCanvasClass:

  
currentObject as shapeClass
xStart as integer
yStart as integer

Okay, run the program. Add a few drawing elements and see what happens if you click on them. If you did everything correctly, they should select and deselect. Your window might look something like this:

Isn't that cool? Note that the objects don't care if you're clicking on a circle, triangle, or rectangle: they all work the same. If we added new objects based on shapeClass (and we will), they'll work the same as well!

That's it for this week: next time we'll make it so we can move those selected objects around (plus a lot more).

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

Next Week

We give SuperDraw some super capabilities.

REALbasic University Quick Tip

Here's a debugging problem for you. Have you ever had a routine that for some obscure reason wouldn't work? The code looks very simple -- it's a method that takes a string, manipulates it, and returns the result -- but the manipulation doesn't seem to work, which is absurd.

Let's look at a practical example. You've written a routine that takes text and strips out all HTML tags (text between < > signs). Here's the code:

  
function stripHTML(s as string) as string
dim s, t as string
dim i, j as integer

// This function strips HTML from the
// passed string.

// We can't modify s, the passed parameter,
// so we copy it to t, which we can change.
t = s
// Find first <
i = inStr(t, "<")
while i > 0
// Starting at position i, we find >
j = inStr(i, t, ">")
if j = 0 then
exit // We're done.
end if

// Strip out HTML tag.
// By not giving mid() a length, it returns
// the rest of the string
t = left(t, i - 1) + mid(t, j + 1)

// Continue searching starting at i
i = inStr(i, t, "<")
wend

return t
end function

The code appears to be correct. But when you run this, an empty string is returned. It's like the manipulation never happens. What's going on? Where's the bug?

I'll let you stew about it for a few minutes. Here's the project file if you want it.

If you give up, the answer's at the bottom of this page, after the letters section.

Letters

This week we've got a couple suggestions for future RBU topics. First Jef writes:

Marc,

I'm a complete novice at REALBasic, and I'm slowly working my way through your lessons. You have a lot of them at this point, so I don't have any idea whether you've addressed this.

I want to create a front end for a basic database. Essentially, I have several different lists that are like a dictionary. I'd like to show a term in one EditField (or on a pull-down menu), and have corresponding information (e.g., a definition of the term) appear in another EditField. Do any of your lessons deal with something like that?

- Jef

Hi Jef! I haven't specifically covered databases in RBU yet, though I have demonstrated a number of data structures, which are a form of databases. Databases are something I have little experience with, but I'm currently learning and hope to do some lessons on them in 2003.

In the meantime, I might suggest (blatant plug coming) a subscription to REALbasic Developer magazine which includes a regular database column.

As to your specific situation, I don't know that a database is required. In fact, what you want sounds a great deal like the RBU Glossary program I wrote to generate the RBU Glossary page. The program looks like this when running:

This allows me to add and edit glossary terms and definitions, and when I press the Publish button, it generates an HTML file all a hot-linked index and everything. The actual terms and definitions are saved in a regular text file, one line per term. No database required.

If there's interest, I might publish the source for this program at some point in the future. Let me know.

Our next letter is from Douglas Beagley, who wants 3D help:

Hello, I'm curious about your opinion of the 3D features of REALBasic. I found an interesting FAQ by Joe Strout, but have found little else describing/critiquing what REALBasic's 3D tools can and can't do. A search on the REALBasic website turned up very little.

Maybe a future topic for RBU? In the meantime, I've just discovered your site and am having a delightful time catching up on your excellent explanations.

-douglas

Thanks for the suggestion, Douglas. Unfortunately my knowledge of 3D is even less that of databases! I write about what I know. As I learn about 3D, I'm sure I'll write some columns on it, but don't expect anything soon.

Meantime, and I don't want to be endlessly self-promoting here, you also might consider a subscription to REALbasic Developer magazine. Every issue includes a column on 3D by Joe Nastasi (who has some 3D articles on idevgames as well), and these are designed for beginners. We also occasionally publish other 3D articles, like Joe Strout's 3D Math Primer in issue 1.2 or his tutorial on building a first-person shooter in 1.3.

Part of the purpose of the magazine is that I can't do everything, as much as I'd love to. Not only don't I have the time and resources, I -- prepare yourself -- don't know everything. So I encourage everyone to take full advantage of the resources that exist.

Speaking of that, keep an eye out for an upcoming announcement regarding a new RBU feature that will help in this regard....

Quick Tip Answer

What makes the debugging problem I presented so tricky is that it's so obvious. It's like that proofreading brainteaser where there are two the's in the sentence and very few people notice.

In this case, we have defined the variable s twice: once as a parameter and once as a variable within the method:

  
function stripHTML(s as string) as string
dim s, t as string

What happens is when s comes into the routine it's filled with the text we want to convert. But then it's replaced by the s in the dim statement, a variable that's initially blank. So our code in the routine might be perfect, but it's just not getting any text to convert!

Tricky. Watch out for this kind of error. One way to avoid this is to use descriptive names for variables instead of simple ones like s or i which are easy to accidentally reuse.


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