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 085

OOP University: Part Nine

Today we're going to take a little break from tedious OOP lecturing and create a simple drawing program. I'll bet that sounds intimidating. But because we're doing this with object-oriented programming, you're going to surprised how easy this is. It took me a mere twenty minutes to create this from scratch!

SimpleDraw

Our goal with SimpleDraw is to show the power of overriding, a concept we introduced in our last lesson. To keep things simple, our program will support just three kinds of objects: circles, rectangles, and triangles. (We can always add more later.)

We begin by adding four classes to our project ("New class" from the File menu). Rename these like this:

ShapeClass will be our generic parent class. For each of the others, make them a subclass of shapeClass. You do this by setting their super to ShapeClass on the Properties palette:

Do that for circleClass, rectClass, and triangleClass.

Now double-click on shapeClass. Add the following method and properties (don't forget that draw has a g as graphics parameter):

Easy, eh? Now the cool thing is that since shapeClass has these items, so do the others by inheritance!

However, shapeClass, while it has a draw method, doesn't really do anything. After all, what's a shape? That's too vague to draw.

But we can do specific drawings for each of the defined shapes, however. So open each subclass and add a draw method.

Here's the method for circleClass:

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

The method for rectClass is almost identical:

  
sub draw(g as graphics)
g.DrawRect left, top, width, height
end sub

Here's the method for triangleClass, which is a little more complicated:

  
sub draw(g as graphics)
dim tx, ty, rx, ry, lx, ly as integer

// Top
tx = left + (width / 2)
ty = top

// Bottom right
rx = left + width
ry = top + height

// Bottom left
lx = left
ly = top + height

g.drawLine tx, ty, rx, ry
g.drawLine rx, ry, lx, ly
g.drawLine lx, ly, tx, ty
end sub

Guess what? We're almost done! We just need to create an interface for our program and give us a way to manipulate our objects.

First, let's create the interface by dragging objects from the Controls palette. Try to make it look something like this:

There are three pushButtons on the left, a separator in the middle, and the box on the right is a canvas -- that will be our drawing area.

Now there's one very important step we must do. The pushButtons should be a control array. Starting the with top one, make them all have the same name, addObjectButton. When you get to the second one, REALbasic should ask you if you want to make this a control array: say yes. When you're finished, the index (on the Property palette under the name) of each pushButton should be 0 for the first one, 1 for the second, and 2 for the third. If not, change the indexes to match these numbers (important).

Good. We're now ready to add code to window1 and create our drawing program. First, let's add a property to window1. This will be our data structure: it will hold a list of all the objects we add. Open window1 and add objectList(0) as shapeClass as a property ("New Property" from the Edit menu).

Next, go to the Controls section and find the Paint event of canvas1. Put in this code:

  
dim i, n as integer

n = uBound(objectList)
for i = 1 to n
objectList(i).draw(g)
next // i

// Draw border
g.drawRect(0, 0, g.width - 1, g.height - 1)

Believe it or not, these few lines draw all our objects! That's because all we do is loop through our object list and tell each object to draw itself. Marvelously simple!

But of course our drawing routine does nothing unless there are objects to draw. We need to add a way to generate objects.

Adding Objects

This is the most complicated part of SimpleDraw. But you'll see it's not complicated at all!

First, we allocate more room in our objectList array. This will be for the new object we'll be adding.

Next, we generate some random sizes and locations for the object. I did it this way for simplicity, but of course you could add a slider control and let the user set the size of the new object that way (or any other way you desire).

Once we've got the size and location determined, we simply create a new instance of the object kind we're creating (circle, rectangle, or triangle). Remember, addObjectButton is a control array -- this one set of code will create all three of our object types. Since we're passed the index of the control array, we can test for each kind of object (0 = circle, 1 = rectangle, 2 = triangle).

Just in case you've messed up creating your control array (i.e. added an extra button), we create a shapeClass object if index doesn't match any of our other cases. Because shapeClass doesn't do anything, adding an instance of shapeClass won't hurt anything (though it still exists as an object in memory).

Our final step is to simply assign the size and location values to the object. Since all our objects have the same properties, this code is the same for all the objects.

Here's the code for the Action event of addObjectButton:

  
dim l, t, w, h, n as integer

// Prepare to add an object
n = uBound(objectList) + 1
redim objectList(n)

// Generate random size and location
w = round(rnd * 20) + 10
h = round(rnd * 20) + 10
l = rnd * (canvas1.width - w)
t = rnd * (canvas1.height - h)

// Create object
select case index
case 0 // circle
objectList(n) = new circleClass
case 1 // rect
objectList(n) = new rectClass
case 2 // triangle
objectList(n) = new triangleClass
else
// Just in case
objectList(n) = new shapeClass
end select

objectList(n).left = l
objectList(n).top = t
objectList(n).width = w
objectList(n).height = h
canvas1.refresh // Redraw with new object

Isn't that simple? The final line simply tells canvas1 to redraw itself, and since we've just added a new object to objectList, that new object is immediately drawn. The effect to the user is we just added a circle, rectangle, or triangle to our drawing area!

Running the program and clicking the buttons generates wonderful artwork that looks something like this:

Obviously, this isn't a full-fledged drawing program... yet. It's surprisingly close, however. If we added ways to select, move, and resize objects, we'd be darn close.

Guess what? With a few changes to our program, we can easily add that behavior! Don't believe me? Tune in next time when we soup up SimpleDraw into SuperDraw!

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

Next Week

With a few simple modifications we upgrade SimpleDraw into SuperDraw!

News

Good news! Those of you who've been following the progress of REALbasic Developer magazine know that we've been waiting for months to receive our periodicals mailing permit so we could send magazines outside of the United States at the much less expensive periodicals rate. Well, the U.S. Postal Service has finally granted our application!

This means we're finally able to ship out magazines to all overseas subscribers. Now normally when you subscribe to RBD your subscription starts with whatever's the current issue at that time. But since we haven't mailed any issues to international subscribers, we're going to start all international subscriptions with the first issue.

Even better, if you're a new international subscriber and you subscribe before we do our initial mailing early next week (Feb. 18th, 2002), we'll give you the same deal! This means you can get all the previously published issues without paying the back issue price. This offer applies only to non-U.S. print subscriptions, and you must subscribe by Feb. 17th, 2003. Take advantage of this once-in-a-lifetime offer!

In case you missed it last week, here's a preview of what's inside the February/March issue.

Marketing Your Software
Kicking things off is a cover story by Electric Butterfly's Dave Wooldridge (of UniHelp fame) on software marketing. If you've ever been intimidated by the various tasks of selling your software, here's the article you need to read. Dave explains about establishing brand identity, getting noticed by search engines, writing press releases, advertising, handling payments, and more.

Designing for Aqua
Next, if you've been struggling to make your program fit in with the Mac OS X "Aqua" appearance, Mike Benonis has some handy tips for you. We've also got a handy "Aqua Checklist" compiled by Doug Grinbergs of mactester.com.

Interview with TouchCAD Creator
If you're interested in graphics, you'll be fascinated by the interview with Claes Lundström, a boat designer who wrote the amazing, full-featured $795 TouchCAD in REALbasic.

Postmortem
For this issue's Postmortem, Thomas Reed details the challenging in rewriting an existing program (Coffee Break Pro) in REALbasic.

Much More
And of course all the regular columns are back: Matt Neuburg's column is on hashing, Thomas Cunningham's has a great primer on graphics from the beginner's perspective (which compliments Thomas Reed's Adanced Techniques' graphics focus), and if you're interested in a challenging subject, be sure to read Didier's "Beyond the Limits" which explains how to do steganography (that's the process of hiding secret messages inside a picture) in REALbasic.

Subscribe today!

Letters

This week we hear from Joe Rice, who has a problem with the getFolderItem function. He writes:

Hi

I'm writing to you as I have a problem. I appreciate the amount of emails you must get asking for help, so I must apologise if you find this annoying. :)

The problem I'm having is with the GetFolderItem function. I'm pretty new to RealBasic (I'm only 15) and finay got this script working, but then, for some reason, it stopped working and I don't know why. I have 6 edit fields and two push buttons, named Save and Load. I want it so when you click on Save, it opens the file 'preferences.text', writes the edit fields data and then saves it. Then Load opens 'preferences.text' and writes the data into the appropriate edit fields.

I have included the scripts below. Like I said before, I had this working, but for some reason, it stopped after I editted another part of my program, completely unrealated to these items. When I click on one of the buttons, it comes up with an error: 'Unhandled NilObject Exception Raised', although I don't kow why. I'm using a dated 3.1.2 (I think).

I would prefer if you could reply to this question through email, as I am usually too busy to look onto the RBU site, as much as it saddens me. :)

I'll answer it in both places, if you don't mind. Part of the purpose of answering questions publicly is that it helps others who are having the same problem.

Thanks a lot for your help
Joe Rice

The Scripts:
This is found in the Save buttons action event:

  
dim file as FolderItem
dim fileStream as TextOutputStream
file=GetFolderItem("preferences.text")
if file <> nil then
fileStream=file.CreateTextFile
fileStream.WriteLine companyName.text
fileStream.WriteLine website.text
fileStream.WriteLine email.text
fileStream.WriteLine phone.text
fileStream.WriteLine fax.text
fileStream.WriteLine address.text
end if
end if

This is the Load buttons action event:

  
dim folder, file as folderItem
dim path as string
dim fileReadfrom as TextInputStream
file=GetFolderItem("preferences.text")
if file <> nil then
fileReadfrom=file.OpenastextFile
CompanyName.text=fileReadfrom.ReadLine
WebSite.text=fileReadfrom.ReadLine
Email.text=fileReadFrom.ReadLine
Phone.text=fileReadFrom.ReadLine
Fax.text=FileReadFrom.ReadLine
Address.text=FileReadfrom.ReadLine
filereadfrom.close
end if
end if

First, I don't find anything obviously wrong with your scripts. However, you are forgetting to check for one kind of error, and I bet that's what's happening. Both your textInputStream and textOutputStream objects have the possibility of being nil if the file couldn't be opened or created. I suspect that's the nilObjectError you're getting. To fix this, simply insert in an if fileReadFrom <> nil then statement after the .openAsTextFile line. (Do the same with the fileStream object in the other script.)

As to why this error would occur, there are several possibilities, but the most probable is that REALbasic can't find the file to open it. Why can't it find it? Well, you aren't giving it much to go on: you're just telling it to open "preferences.text" (with no path to it). Since you aren't giving it a specific, repeatable location, REALbasic looks for it in its default locations. Here it gets tricky, especially with an older version of REALbasic. When you're in the IDE, REALbasic's default path is to look within the REALbasic folder. But when you have a compiled application, REALbasic looks inside the folder where that compiled app resides.

A far superior way to do this is to put this file in your Preferences folder (I'm assuming since its name is "preferences.text" its a preference file for your application). Don't know where the Preferences folder is? No problem: RB will tell you!

Just change your getFolderitem code to this:

 file = preferencesFolder.child("preferences.text") 

What this does is use the preferencesFolder function to return a folderitem pointing to your Preferences folder, wherever it may be, and regardless of your operating system. Then you add the child method, passing it the name of the file you're seeking: that will find a file by the name you pass that resides inside the parent folder (the user's Preferences folder).

By doing it this way you're guaranteed this code will always work. You still always need to check that the file exists, of course -- the first time an app runs the prefs file probably isn't there. I'd also recommend you change the name of your file from a generic "preferences.text" to something unique that includes the name of your program since it will now be in a more public location (with lots of other prefs files).

Hope this solves your problem!

P.S. I sent this to Joe earlier and he wrote me back to say it indeed did fix things for him.


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