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 091

OOP University: Part Fifteen

Last week we added more objects to SuperDraw, and while we could continue doing that forever, I think you get the general idea. So let's begin to wrap up SuperDraw with a few essentials and move on.

Note that our final two SuperDraw lessons deal with advanced polishing, so I won't explain everything in too much detail in the column, but you will have access to the full source code of the final SuperDraw project. So bear with me if I speed a bit: we've got a lot to cover.

Interface Niceties

There are several interface elements we've left out of SuperDraw, so let's fix that now. The first is a standard Select All command.

Select All

Open the menu object of our project and add a separator and a "Select All" menu item (its name should be EditSelectAll). Give it a CommandKey of "A" (no quotes).

Now open window1's Code Editor and go the Events section. In the EnableMenuItems event, put this code:

  
if canvas1.objectCount > 0 then
editSelectAll.enabled = true
end if

Then add a new menu handler: go to the Edit menu and choose "New Menu Handler" and select EditSelectAll and click OK. In the handler that's created, put this code:

  
canvas1.editSelectAll

What we've done is added a menu, enabled it when there's more than zero objects in the drawing, and when that menu item is executed, it tells the drawCanvasClass object to select all.

But wait! Our drawCanvasClass doesn't know how to select all... yet. So let's add that method.

Open drawCanvasClass and add a new method (Edit menu, "Add method") named editSelectAll and put in this code:

  
dim i, n as integer

n = objectCount
for i = 1 to n
objectList(i).selected = true
next // i

me.refresh

As you can see, this is simple code. It just iterates through every object in our data structure and sets their selected property to true. Then it refreshes (forces a redraw) the canvas so all the objects are redrawn with selection handles (that way the user can see they are now selected).

Deleting Objects

The next important interface item we've left out is a way to remove objects. The simplest way to implement this is to allow the user to press the Delete key which will delete whatever objects are selected. Sounds easy enough, but there are actually a couple steps we have to take to get this work.

First, we need to open drawCanvasClass and add two new events (Edit menu, "New Event"). The first is a simple Open event which takes no parameters and returns nothing. The second is keyDown, which takes key as string as the parameter and returns a boolean.

We add these because we're going to override the originals by putting code in them, yet we want the object to still have those events in case they're needed outside of the class.

In the open event, put this code:

  
// We need this to accept keystrokes
me.acceptFocus = true

// Pass on open event
open

For drawCanvasClass to receive keyboard events, we must set it to accepting the focus. Then we pass on the open event so that our canvas1 object has an open event.

Next, we need to add code within the keyDown event. This will check to see if the user has pressed the delete key:

  
dim i, n as integer

n = objectCount
// User pressed delete key
if key = chr(8) then
// We use a while/wend loop so we can
// dynamically restart the loop.
i = n
while i > 0
if objectList(i).selected then
objectList.remove i
n = n - 1 // There's one less object now
i = n // Restart loop
else
i = i - 1
end if
wend
me.refresh
return true
end if

// Pass event on...
return keyDown(key)

As you can see, this code is a little tricky. We can't use a for-next loop like you might expect, since we're potentially deleting multiple objects. Say there are 10 objects -- we remove one, but our for-next loop still goes up to 10 and when we refer to objectList(10) the program crashes. So instead we use a while-wend loop and we count downwards, starting with the last object. This way the length of our loop is dynamic.

If we've removed objects, we refresh the canvas and return true. If any other key has been pressed, we simply pass that on the new keyDown event, so that canvas1 can handle it (if it wants).

Cool! Now our program lets the user select all and delete objects. But we're not done yet.

Expanding Object Capabilities

One thing we haven't talked much about is expanding the capabilities of our objects. We've added new objects, but in truth our objects are fairly basic. There are many more characteristics we could set and change. For instance, objects could have a color setting, a line-weight setting, or a fill setting. Some objects, like textClass, could have unique settings like bold or italic. Implementing all of those is beyond what I want to do for RBU, but I will add one capability just so you can see how it is done. Other capabilities would be added in a similar manner.

Adding Line-Weight Capability

As an example of a user-modifiable setting, let's add a line-weight setting to our objects. This sounds like it will be a lot of work, but actually it's remarkably easy.

Open shapeClass and add a new property (Edit menu, "New Property"): lineSize as integer. Set it to "private" (the option is called "protected" in RB 5). This will contain the current line size. Since we don't want outsiders modifying with our data structure, we won't allow anyone but us (shapeClass) to modify the setting. Thus we'll need to create routines that allow outsiders to get and set the value.

We need to add two methods (Edit menu, "New Method"). The first is getLineSize with no parameters but returning an integer. The second is setLineSize which doesn't return anything, but takes newSize as integer as a parameter. Here's the code for these routines:

  
function getLineSize() as integer
return lineSize
end function

  
sub setLineSize(newSize as integer)
if newSize > 0 and newSize < 25 then
lineSize = newSize
end if
end sub

These methods allow outsiders to get and set the lineSize value. This way our data structure (a very simple one in this case) is hidden from outside sources. We can change the way this value is stored without modifying our external code. Note that the setLineSize routine ensures that the value is within an arbitrary range (greater than zero and less than 25).

Now you might think that since we've added a new lineSize property, we need to modify every single object's drawing method, since how else will that drawing routine know to draw with the correct line size?

For many object extensions, this is true. For instance, if we added a fill setting, we would need to modify our objects' drawing routines to draw either with a fill or without, depending on the current state.

But lucky for us, none of our objects actually specifies a line weight in its drawing routine. For that reason, we can take advantage of our object architecture and handle the new drawing specifications with just two lines of code!

Within shapeClass, open the draw method. Make it look like this:

  
g.penHeight = lineSize
g.penWidth = lineSize

paint g
drawSelection(g)

As you can see, we've just added two lines of code to set the line-weight. Since draw executes before any object-drawing, the line-weight is set before the object begins to draw. As long as the object doesn't override that with its own line-weight setting, the object will draw with the line weight we specified (lineSize). Isn't that cool?

However, if you run SuperDraw now, you won't see anything different. That's because we haven't added any way for the user to modify an object's line-weight!

Open drawCanvasClass and go to the KeyDown event. After the end if and before the return keyDown(key) line, insert this code:

  
if key = "-" and currentObject.selected then
currentObject.setLineSize(currentObject.getLineSize - 1)
me.refresh
end if

if key = "+" and currentObject.selected then
currentObject.setLineSize(currentObject.getLineSize + 1)
me.refresh
end if

As you can probably figure out, this watches for the user pressing the minus or plus keys and adjusts the line-weight accordingly.

However, if you run SuperDraw, you'll see we have a new problem: no objects display! Select All selects the objects, but they're empty!

Or are they? When you stop to think about it, you'll realize something else has happened. Our new property, lineSize, defaults to a value of zero on program launch. Since we never set it to anything else, all our objects are drawing with a line-weight of zero!

This is easily fixed through a two-step process. First, open shapeClass and add a new method, init (no parameters or returns). Inside it put this code:

  
lineSize = 1

Next, we need a way for this method to be called, and that needs to happen right after it's created. So let's open drawCanvasClass and go to the addObject method. Find the line that says n = uBound(objectList). Right after it add:

  
objectList(n).init

There, now run the program and you should be able to select an object and make its line-weight thicker or thinner by pressing the + or - keys.

But what's this? Does you screen look like this when you give an object a thicker weight?

We've discovered a minor bug. Since our drawCanvasClass draws a border around itself, the border also gets a thicker line!

But that border is no longer needed. Now that we've made drawCanvasClass accept the focus, the object automatically has a system-provided border around it. So let's just comment out that line. Open drawCanvasClass' paint event and go to the last line. Press Command-' (or type ' or // at the beginning of the line) to comment it out and disable it.

Now when you run SuperDraw it works! You'll notice some objects, like the textClass object, don't use the line-weight feature. The pictClass object does use it when it doesn't have a picture, but perhaps it shouldn't, as that looks strange (the "no picture" x-box is supposed to be just a placeholder, not a graphic). But it'd be easy to override this by adding penHeight and penWidth settings within the pictClass' drawing routine.

There are many, many enhancements you could add to these objects. I already mentioned a few, but here's a cool one: you could add a zoom feature to drawCanvasClass. This would be a property (most likely a double) that tells the objects at what percentage to draw themselves. For instance, if zoomLevel is 1.0, that's 100%. If zoomLevel is 0.5, then that's 50%. If zoomLevel is 2.5, then that's 250%.

Of course there's a little more to it than that, since the canvas' itself would have to support scrolling, if the picture's enlarged. So instead of drawing directly on the graphics property of drawCanvasClass, draw onto the graphics property of a picture. Then that picture would be displayed within drawCanvasClass's paint event, and the picture could be enlarged or reduced via the drawPicture command.

The possibilities are endless! In fact, I'm curious what readers will create. So we're going to have a contest! See below for the announcement and official rules.

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

Next Week

We finish SuperDraw by adding file save and load commands.

SuperDraw Object Contest

SuperDraw's object-oriented design makes it easy to add new object types and expand existing ones. The possibilities are endless. Have you added your own object types to SuperDraw? Do you have an idea for a new object type or an enhancement to an existing object? Send it to me!

REALbasic University is having a contest! Send in your SuperDraw objects and enhancements. We'll judge them and give out awards and prizes to the best entries. You could win a subscription to REALbasic Developer magazine, an RBD T-Shirt, or other cool prizes!

Contest Rules

  • All entries must be received by April 30th, 2003.
  • Entries may be either a new drawing object class for SuperDraw, or an enhanced version of SuperDraw with new object capabilities and features.
  • All entries must include entrant's Name, Email Address, and Mailing Address.
  • Multiple entries by the same entrant are acceptable.
  • All entries must be compressed in an archive and include the REAlbasic project file and all resources required to run the project.
  • Entries that don't run will be rejected.
  • Entries will be judged on originality, effectiveness, and code efficiency.
  • By submitting an entry, you give permission for REALbasic University to publish your name and your source code/REALbasic project in a future column on the RBU website.
  • All decisions by REALbasic University with regards to this contest are final.
  • Entries must be sent to: rbu-contest@stonetablesoftware.com

Winners will be announced in May 2003, so get coding!

REALbasic Developer 1.5 Preview

The next issue of RBD is being printed right now, and I'm here to give you a sneak preview!

Our primary focus in this issue is REALbasic 5, with not one, but two in-depth articles.

REAL Software engineer Joe Strout explains the new compiler that is at the heart of the new environment. And even if you think you know RB5, Matt Neuburg is sure to reveal some secrets in his detailed round-up of all the new features.

We've got more from Joe as he brings us a fascinating look at path-finding algorithms. The obvious use is finding a way through a maze, but you should study this article as the techniques are there for many other uses.

For our Postmortem, Jacob Lauritzen writes about SmartLaunch, and I interview Michael Herrick, author of Spamfire, one of the most successful Made-With-REALbasic products on the market.

In other news, I wanted to bring a little fun into the magazine, so I'm pleased to announce Hacker, an original comic strip created just for RBD from Dan Wilson (issue 1.2 cover artist).

It's about the daily life of an enthusiastic REALbasic programmer. We'll feature a new cartoon in every issue!

Plus, we'll have all our usual columns and topics, news and reviews. If you aren't a subscriber yet, order today and don't miss out!

Letters

This week we've got an interesting question from New Zealand:

Hi Marc,

Thanks a million for your excellent tutorials. I took up REALBasic as a lot of programs I use on my Mac are quite old and will never make it to MacOS X -- most of them have not been developed for years. They still work fine in Classic but there are just some features which are missing (e.g. colour) -- so in all naivety I thought "Hey, I'll write my own..." which meant learning how to program in the first place. Coming from (and being spoiled by) HyperCard I chose REALBasic because the code is rather readable (I can't stand C), it is cross-platform to a degree, and now uses vectorgraphics (which gave me the final push to buy RB5).

Anyway, I often find dozens of programs and examples which show a concept -- but rarely something which is of any direct use. And the same is often true for shareware/freeware programs, aka "not another calculator!" What is missing is software which can help me do actual work, and advice on how to program this software.

In my case I want to create a DNA & protein sequence analysis program (my favourite GeneJockey was last updated in 1997) and a Plasmid drawing application (the still widely used MacPlasmap was last updated in 1990). For the later your SuperDraw tutorial is a godsend (and I can already see how to extend the objects with border and fill colours).

My biggest problem is making "curved arrows" (especially of the tet variety in the map - that probably requires some kind of grouping, otherwise the fill colour would spread between the two arrows), and I'm waiting with baited breath as to which object types you are going to introduce :-)

As for future projects -- the one which immediately comes to my mind is styled text operations. Every modern application should support different fonts and colours, but every time I perform a string operation on an editfield -- like ReplaceAll(Editfield, "a", "z") -- it loses all formatting.

How do I handle it in a cross-platform way that this does not happen? I'm stumped, and unfortunately the REALBasic documentation is not much help (I also bought "REALBasic for Dummies" and "REALBasic: The Definitive Guide", 2nd ed. but neither could give me the info I needed). What I envision is a ReplaceAllStyledText method which could do it -- and the fact that several REALBasic programs are doing it (like word processors) means it's doable -- but I'm stuck on how to do it ...

So far for today -- thanks again for your excellent work. If my programs ever get off the ground it will in no small part be due to RBU ... ;-)

Best Regards

Dr. Markus Winter

First let me say that I'm glad SuperDraw has been a help to you. I certainly learned a great deal in writing it -- object-oriented design is indeed cool and a huge time-saver.

Second, I agree with what you say about the lack of concrete examples. That's one reason I often try to base my tutorials on real programs I wrote for my own use. Ancient "Hello world" demos are pointless, and as your styled-editField comments reveal, modern programs must be full-featured, period, or they are going to fail. As I finish up "OOP University" and we return to our regular tutorial schedule, I will keep your comments in mind and see what I can do to create useful and practical examples of projects.

Regarding the curved arrows, that doesn't sound too difficult, though it would involve some math calculations (yuck). One approach would be to use RB's vector graphics and make a subclass of the arcShape class, adding capability to draw an arrowhead on the end. Another approach would be to use a pre-drawn arrowhead, place it into a object2D object, and use the object2D's rotation property to rotate the arrowhead the same direction as the curved arrow (you'd have to do some math to figure out the direction and position to place the arrowhead).

Hopefully that can get you going. If others are interested in this, perhaps I can make it the focus of a future column. Or perhaps someone will do this for the SuperDraw Contest!

As to the styled-editField, you have an excellent point. Personally I think this is a feature that ought to be integrated into REALbasic, but at this time it is not. The workaround is complicated. I myself have done this (my Z-Write word processor does a lot of styled text manipulation), but it's not easy. I've thought of releasing my routines but hesitated for competitive reasons. After all, I put a lot of time and energy into figuring out how to do it. However, I haven't made a final decision: perhaps one day I will do an RBU series on parsing styled text.

In the meantime, I'll give you a few hints and tips. For text to be styled (hold font, color, and other details) we must have a way store that info. The Mac OS has traditionally held the style info separate from the text. The advantage of this is that it's easy to for applications that don't know how to handle the style info to just accept the text (via the "paste clipboard" command, for instance).

Managing the style info is yucky, however. Each "style run" (continuous section of text with the same style settings) must know its starting point based on the character number. For example, let's assume this is our styled text:

This is some test text.

The first style run begins at character 1. The second style run starts at character 10. The third (reverting to non-bold style) begins at 14.

If we insert letters within the first style run:

This is NOT some test text.

Now everything has changed. Our style starts are wrong and must be updated (add four to each number) to compensate for the new characters inserted.

Of course, in REALbasic, the program normally handles all this transparently for us, in the editField. We never directly need to modify the styles.

However, a find/replace operation, like you mention, loses the styles if it's done on just the editField's text. The solution is to parse the style data itself and adjust that data as needed (for instance, if a replace inserts or removes characters).

REALbasic allows us separate access to text and style data via the text and textStyleData properties. Parsing the textStyleData is the tricky part, as this must be done with a memoryBlock (see REALbasic's docs for info on this complex object).

The memoryBlock approach is the fastest and most efficient, but it's not cross-platform (Windows systems use different text style info).

A simpler approach, if speed isn't critical, is to use an invisible editField and programmatically select the find text and replace it. If you do that, the editField itself will handle managing the style info. Since it's invisible, it will be much faster than a visible editField, and of course the user won't see the text flickering as the find-replace takes place.

Here's a simple routine I created as part of a special FRStyledEditClass class:

  
function styledReplaceAll(e as editField, theFind as string, theReplace as string) as integer
dim i, count as integer

// Check for errors in parameters
if theFind = "" or e = nil then
return 0
end if

// Fill self with text and style
me.setTextAndStyle(e.text, e.textStyleData)

// do find/replace
i = inStr(me.text, theFind)
while i > 0
me.selStart = i - 1
me.selLength = len(theFind)
me.selText = theReplace
count = count + 1

i = inStr(i + len(theReplace), me.text, theFind)
wend

// Replace original text and style
e.setTextAndStyle(me.text, me.textStyleData)

// return number of replacements
return count
end function

As you can see, this is pretty simple. It first copies the styled text into itself, then looks through the text for the search string. When it finds it, it selects it, then replaces the selected text with the replacement string. Finally, it copies the changed text back to the original editField.

To use it, just drag an instance of it to your main window and set its visible property to false (unchecked). I named my hiddenEdit, so I call it like this:

  
n = hiddenEdit.styledReplaceAll(editField1, findField.text, replaceField.text)

The variable n will contain the number of replacements (zero if theFind wasn't found).

Here's what the example program looks like running (after the replace):

You can grab the class and the demo project here.

This is slow if there are many replacements (the "e" replacement in the picture takes a few seconds), but it's probably usable for 75% of the situations you'll encounter. There are a few enhancements that could be made.

For instance, if you could test to see if theFind and theReplace are the same length, and if so, use REALbasic's standard replaceAll routine. That works because if the replacement text isn't going to change the length of the text, the positioning of the style runs won't change regardless of the text.

Also, the routine is currently case insensitive. You can rewrite it using REALbasic's strComp function to make it case sensitive.


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