| |||||||||||||||||||||||||||||
|
| |||||||||||||||||||||||||||||
Print This Article REALbasic University: Column 054
SimplePaint: Part IOccasionally, my boss at the printshop where I work brings one of his children in for a few hours. The youngest now is age 5, but a few years ago the oldest was that age. Kids that young have short attention spans, and it was always a challenge finding something entertaining to keep them out of trouble. Drawing always amused them, and we discovered they were fascinated by drawing on the computer. So I'd load up a paint program on an extra Mac we have and let them play around. Initially I tried Adobe Photoshop, since that's the bare minimum of a paint program in my book. Of course that was far too complicated for the girls, so I switched to ClarisWorks. That was sort of satisfactory, but I discovered I had to keep a close eye on them: without supervision they'd click outside the window, bringing up the Finder, and then they'd create all sorts of havoc, moving folders around and typing gibberish for file names. For three or four seconds I considered searching for a free "KidPaint" type program on the Internet. But I was too lazy to find and test all those programs. Instead, the idea quickly hit me: I'd write my own. I loaded up REALbasic and in just a few minutes I had a very basic paint program. It was so much fun I quickly added some cool sophisticated features. The end result is a very simple paint program specifically designed for kids. Sure, there's probably cooler or better stuff out there, but this did exactly what I wanted and the price was right.
Program SpecificationsSince this program was targeted for children, I had a narrow range for my specifications. I came up with the following mental list of criteria:
I decided that since the girls found the whole "tool palette" thing confusing (it's fundamentally modal), I wouldn't offer the traditional drawing program options of letting you draw circles, lines, or rectangles. Instead, there'd only be a pencil tool. In this program there's only one mode: painting with pixels and that's it. Since the girls rarely wanted to go back and edit their creations, I decided not to bother creating a way to reopen previous drawings. The girls would draw something, save it, erase the screen, and start again. There was also no point in creating a way to print the pictures: if they wanted, I'd print the saved images for them out of Photoshop.
A Blank ScreenIn the old days of REALbasic, hiding the menubar was a difficult trick requiring system calls or a plugin. With REALbasic 4, however, it's easy. Just uncheck the MenuBarVisible property of a window! Create a new project. Rename window1 as paintWindow and give it these settings: ![]() Note that checking the HasBackColor property gives the window a white background, which is what we want since that will be our drawing area. Go ahead and save your project in a "Simple Paint" folder with "SimplePaint.rb" as the name. (Note that ".rb" is the extension REALbasic uses for project files under Mac OS X, so it's good to use that even under earlier operating systems.)
Remembering the DrawingSince we'll be doing various kinds of manipulations of our drawing (saving, editing, etc.), it's a good idea to store it inside a picture variable. Open paintWindow and add a new property (Edit menu, "New Property") p as picture. As long as we're here, lets add some other properties we'll need. ![]() To get SimplePaint working as a bare bones paint program, we don't have to define these right now, but we're thinking ahead. The c property will hold the color we're currently painting with. The xWidth and yWidth properties represent the size of the paintbrush we're using. Eventually we'll add a way to modify these properties so the child can pick different colors to draw with and change the size of the paintbrush.
InitializingBefore we can begin drawing into our picture variable, we must initialize it (in technical terms, allocate [set aside] memory [storage] for the variable). We do that with the newPicture method. The picture must be as large as our window (which is as large as the main screen, since we checked the window's FullScreen property). We also want the picture to be full color, so we use 32 as newPicture's parameter (meaning the picture will be 32-bit color picture). (On machines without much memory, you could get by with 16 or 8, though 8-bit color is only 256 colors.) Since this must happen early in the program's launch, let's put it in our drawing window's Open event. Put this code in paintWindow's Open event:
Note that if there isn't enough memory for p, we quit the program. Not exactly the most polite way to quit, but better than a crash. (If you wanted, you could try to allocate the picture at 32 bits, and if p = nil then try again at 16 bits. Just make sure you again check that p isn't nil after that.) Once we've got a valid p, we assign that to paintWindow's backdrop property. That means whatever the picture p looks like will be the background of paintWindow. This way we don't have to do any drawing or redrawing of p -- that happens automatically whenever paintWindow needs it. Finally, we set the default sizes of our xWidth and yWidth values.
Watching the MouseAll painting programs work on the same basic principle: when the user clicks the mouse button, a pixel is drawn at that click location. Ours is no different. Go to the MouseDown event of paintWindow and add this: return true This just tells REALbasic that we'll be handling the MouseDown event. The default response is to return false, meaning that we're ignoring the MouseDown. By returning true, REALbasic will now allow the MouseDrag event to happen. And that's where we'll do most of our work.
In the MouseDrag event, we'll add this code:
You might wonder why we bother declaring the x2 and y2 variables -- wouldn't it be easier to just pass x and y on to drawPixel? Yes, it would, but we're again thinking ahead: in the future we may want to manipulate those x and y properties. The final line passes a graphics object (from p, our picture), and the drawing coordinates, to our drawPixel routine. What drawPixel routine you ask? The one we're about to create!
Drawing a PixelTo actually get anything to draw, we need to create that drawPixel routine we just called. Add a new method (Edit menu, "New Method") and name it drawPixel. Put g as graphics, x as integer, y as integer on the parameters line. The code is fairly simple:
What are n1 and n2? They are the center point of the "pixel" that's going to be drawn. Remember, our "pixel" isn't actually one pixel in size: it's the size of xWidth and yWidth (which we set to 9 by default). We'll draw that extra-large pixel using REALbasic's fillOval method. When REALbasic draws a circle, the coordinates where you start drawing that circle are the upper left of the box the circle is drawn in. For example, the red circle below is drawn by giving it the dimensions of the outer box (upper left corner and width and height). ![]() In a sense, a circle is just a box with extremely rounded corners! But for our purposes, we want the center of the circle to be the x and y coordinates where the user clicked. That means we must do a little math to adjust those drawing coordinates so they're half the width and height of the circle off from the original coordinates. So by starting to draw the circle to the left and up, the center of that circle is at the original coordinates. Next, we set our drawing color to c and we draw the circle (oval, since the width and height could be different). Finally, we tell paintWindow to refresh (update) the area we just drew. Simple, eh? Try it. The program works and will let you doodle: ![]() But there's no way to change the drawing color, the size of the brush, or save the picture. We'll work on that next time. If you would like the complete REALbasic project file for this week's tutorial (including resources), you may download it here.
Next WeekWe enhance SimplePaint with a color palette and other features.
NewsThe development of REALbasic Developer magazine is proceeding well. Articles have been written and edited, and we're working on the layout of the first issue. Here's a sneak peek of what the cover of the premiere issue looks like: ![]() Subscription pre-orders are now available at 25% off the normal price.
LettersSeveral people wrote to tell me that our last project, Zoomer, does not work under Mac OS X. Well, it half works: the editField is magnified, but the text inside it is not. Apparently some of the bugs that used to exist with the drawInto method still exist under Mac OS X (in the past, drawInto had a bug where it would only draw the first few lines of the editField and not draw text that had been scrolled). First, I want to apologize for not testing Zoomer under Mac OS X. That was an oversight. More and more people are using OS X for development and I'll try to make sure everything we do for RBU works for both platforms. At minimum, if something won't work, I'll let you know. Second, if you'd like to get Zoomer working under Mac OS X, you can try this workaround. It won't fix the editField's text not being shown bug, but it will give you something to magnify. Open the drawZoom method. Do a find/replace with these settings: ![]() Click the "Replace All" button. What this does is let Zoomer magnify the entire window instead of just editField1. That gives you some stuff to look at -- if you want, drag some more controls onto window1 to give you more things to zoom. Next, Julian writes with a problem with popupMenus.
The problem you are describing is very common. While your solution is one method, it's complex. A simpler solution, which is what I frequently use, is to create a "myChange" variable that tells my code it's a programmatic change, not a user action. So your initialization event would look like this:
Then, within the Change event, you have code like this:
I use this technique frequently when I implement an undo/redo system. That's because an undo system generally saves a user action in some kind of buffer (in order to possibly undo it later). The problem is when you need to redo a user's old action or restore data to its previous state via undo, that action will be regarded as a user's action: your program can't tell the difference between something you do programmatically (via code) or something the user is doing. But creating a boolean variable that tells the routine that this is a programmatic action means you can avoid doing what normally might happen when the user does that. Still, this solution requires extra work on your part. What would be nice -- we ought to suggest it to REAL Software -- would be a built-in global boolean that would return true if a call to a routine was made programatically and false if it was made by the user. You'd still have to enclose your code with a check to this value, but you wouldn't have to worry about setting the boolean yourself. As to your other comment, about having a description area within methods, it's not a bad idea. Until REAL Software adds that, most people add a dummy method to describe a class, something like this: ![]() 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.
| |||||||||||||||||||||||||||||