| |||||||||||||||||||||||||||
|
| |||||||||||||||||||||||||||
Print This Article REALbasic University: Column 091
OOP University: Part FifteenLast 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 NicetiesThere 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 AllOpen 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:
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:
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:
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 ObjectsThe 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:
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:
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 CapabilitiesOne 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 CapabilityAs 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:
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:
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:
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:
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:
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 WeekWe finish SuperDraw by adding file save and load commands.
SuperDraw Object ContestSuperDraw'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
Winners will be announced in May 2003, so get coding! REALbasic Developer 1.5 PreviewThe 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!
LettersThis week we've got an interesting question from New Zealand:
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:
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:
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:
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:
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 See the REALbasic University Archives
REALbasic University contents ©2001-2004 by Marc Zeedar and REALbasic Developer. All Rights Reserved.
| |||||||||||||||||||||||||||