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 044

RBU Pyramid XVII: Polishing Part I

My original intention when creating this series of tutorials, building a complete game from start to finish, was to have everything carefully planned (and even written) in advance. That way I'd leave nothing out and the steps would follow in a logical order. Unfortunately, reality is what happens while you're making plans, and of course things didn't work out quite the way I planned.

One consequence of this is that there are a slew of small fixes, enhancements, and changes I made to RBU Pyramid during its development that we haven't covered, yet none of these are significant enough to warrant a column of their own. So today I present a hodge-podge of code snippets and polishes. There are a half-million of these to add, so this will be a two-part lesson. We'll also jump around our project quite a bit, so stay focused and follow along carefully.

Adding the RBU Logo

While developing RBU Pyramid, I realized that it would be a good idea to release the final version of the program as a freeware product. After all, the source code is fully available, so it's not like I can sell the game, but I could promote the game as freeware and use it to publicize REALbasic University (and in turn, REAlbasic Developer magazine).

It was this thinking that led me to name the program RBU Pyramid, add appropriate RBU and RBD links in the About Box, and add the RBU logo to the main game screen. That's what we're going to do now. It won't be difficult.

Open your project file and double-click on gameWindow. Drag on a new Canvas object. Name it logoCanvas and set its Left property to 13, Top to 14, Width to 83, and Height to 48.

Now add this picture to your project (download it, place it in your Linked Graphics folder, and then drag it into your project window):

Once that's imported, select logoCanvas and set its backdrop property to rbusmall (that's the name of the above graphic).

Perfect! Looks cool, and it's in the right place, away from our pyramid of cards. Next we just need to add some basic functionality.

First, it would look better with a border, so let's go to the control's Paint event (press option-tab while selecting logoCanvas to open the Code Editor). Put in this line of text:

  
g.drawRect(0, 0, g.width - 1, g.height - 1)

Next, since we want it to open the About Box when it is clicked, put this in the MouseDown event:

  
aboutWindow.showModal

That's it! Try running the program and testing it just to make sure it works.

Adding a New Game Menu

Currently RBU Pyramid starts a new game when you launch it, but there's no way to start a new game when the program is running. Obviously, that's not good. Also, under Mac OS X the File menu's Quit item is automatically moved to the application menu, and since that's the only menu item, the File menu is completely blank (under Mac OS X)!

Double-click on the Menu object within your project. Within the menu window, go to the File menu and click on the blank space underneath the Quit menu. Type in "New Game" (no quotes) and set the CommandKey property to N. Press Return to lock in your settings. Drag the menu item up to the top position. Since the program under Mac OS Classic will have a Quit item on the File menu, it's a good idea to add a separator between the two commands. Go back to the blank line and type a hyphen (-) and press Return. Drag the created separator up and put it between the other commands. Under Mac OS X, the separator won't show up (since it would be the last item on the menu and that would be silly).

We now have our menu command, but we need to activate it. Now here's an important thing to remember about how REALbasic works with menus. The system is so slick and transparent it's easy to forget how powerful it is. But it can be confusing at times, so let me explain how the system works.

Beginning RB users quickly understand the concept of menu handlers: the handler is like a method with the name of a menu command. Whatever code's inside that handler gets executed when the user chooses that menu item. Easy enough.

But after that intial bit of understanding, they are often puzzled at why you can insert menu handlers into so many places. After all, each window has it's own menu handlers, as does the application class, and custom classes as well! Why so much duplication? Or is it duplication?

Here's the trick: think about how menus work within an application. There are some obvious things. For instance, if something is selected (text or an object), the Edit menu should be available with Cut and Copy and maybe Paste (if there's something on the clipboard). However, if no object or data is selected, there's no reason for the Edit menu to be active.

And what about a program like a word processor, that lets a user open multiple documents? When all the documents are closed, you surely want the Quit, New Document, and Open document commands to be available!

And that's the key: menu commands that work on data are always inside a window. After all, no data can be selected or pasted if there's no window to view the data!

Menu commands that don't require data, such as Quit, Open, or New, don't belong in a window. That's because if that window is closed, those commands wouldn't be available!

Once you understand that fundamental aspect of menus, it's easy to see where menu handlers go. New and Open menu items go within your program's Application Class (often named App). Since the App class is there when your program is launched and is the last thing closed when your app is quit, those commands will always be available regardless of the state of the windows.

REALbasic always first checks with the Application Class for establishing the state of menus, then the frontmost window, and last of all individual controls. The benefit of this priority method is that each more specific object has the ability to override the more general object's setting. Let me give you an example.

Let's say you had a situation where you had a ListBox of data and you wanted the user to be able to copy and paste these items. (The kind of data they represent is irrelevant -- they could be pictures or folderItems.) REALbasic normally automatically activates the Copy and Paste commands as needed for EditField text editing, but since it doesn't know what kind of data the ListBox represents, it can't automatically support that. But you could easily create a custom class of ListBox type and add in menu enablers and handlers within that class. The effect to the user would be that the Copy and Paste commands are available when data is selected within the ListBox.

You see how powerful this is? Your object -- the Window or ListBox or EditField or Canvas -- knows how to turn on menus and what to do when the user chooses the command! When the user clicks on the EditField, the Copy command copies text. When they click on the ListBox, it copies folderitems (files, or whatever data you want). When they click on a canvas, it copies a picture.

If an object isn't available or isn't visible, the menus for that object aren't available either. This makes menu handling much easier for you, the programmer, since you just add menus to the objects needing them.

This also gives you a way to override menu functions: the same menu command can have different effects depending on the context. For instance, choosing a Preferences command while a document is open could offer settings for that specific document, but selecting Preferences when no documents are open would offer global program settings.

In Detail

You'd do this simply by adding a menu handler into both your App class and your window object. In the app class you'd bring up the global preferences dialog, and within the window you'd bring up the document specific preferences dialog. Simple! The only thing to remember is that within the window menu handler, you must return true at the end -- otherwise the menu handler of the app class would run as soon as the window one was finished.

You can easily test this by adding fileNewGame menu handlers to both the app class and gameWindow and putting in msgBox commands in each stating which is which. Then when you run the program and choose "New Game" from the File menu, the appropriate message will be displayed depending on whether or not gameWindow is visible.

In the case of RBU Pyramid, we only want one game to take place at a time, so the approach we'll take is to put our fileNewGame menu handler within our App object. (You can add the menu handler by opening App, selecting "New Menu Handler" from the Edit menu, and choosing fileNewGame in the popup menu that's displayed.)

Of course before the menu is active, we must enable it. Add this line anywhere inside app's EnableMenuItems event.

  
fileNewGame.enabled = true

Now we just need to make the command do something. It seems like that should be as simple as inserting a call to our newGame method within fileNewGame.

However, adding this command brings up another problem: what happens if the user's already in the middle of a game? It would be horrible if the player was in the middle of a record-breaking game and accidentally hit Command-N and a new game was started without any warning, wouldn't it?

So yes, we must add a confirmation dialog. First, let's put in our code for the menu handler:

  
// Confirm restart
gDialogReturn = ""
confirmDialog.showModal

if gDialogReturn <> "" then
gameWindow.endOfGame
newGame
end if

We'll reuse the same gDialogReturn string we used for other dialogs to tell us what the user did in the dialog (clicked okay or not). Since all we need is a yes or no, we'll just assume if there's anything at all in the string it's a positive response -- the user wants to abandon the current game.

If so, we run our endOfGame routine (so the user can get added to the high score list if appropriate) and then call newGame.

Now let's create confirmDialog. Add a new window to your project (File menu, "New Window"). Give it the following settings (via the Properties palette):

Onto the dialog we're going to want four items: two pushButtons, a staticText, and a canvas. Make them look something like this:

Name the left button cancelButton and the right button okButton (and set their respective cancel and default properties as well).

The size of the canvas should be 32 wide by 32 tall. That's because it's going to draw a standard "caution" icon. If you select it and press Option-Tab, you'll be taken right to canvas' Paint event, were we want the following line of code inserted:

  
g.drawCautionIcon 0, 0

Good. Now go to cancelButton's Action event and put in this code.

  
gDialogReturn = ""
self.close

That just makes sure our global string is empty and closes the dialog.

In okButton's Action event, put this:

  
gDialogReturn = "Yes"
self.close

This also closes the dialog (important), but puts a "Yes" into our global string.

Go ahead and run the program. It should run fine, and the "New Game" menu command should work. However, you might note an ugly detail: even when you don't start playing a game by clicking on any of the cards, choosing "New Game" always brings up the confirmation dialog. Now I don't know if you play pyramid the way I do, but I often will do several redeals before starting a game, waiting for a decent shuffle. But if every time I press "New Game" to redeal the cards I have to answer a silly dialog, I'm going to be annoyed. So let's fix this.

Let's add a global variable, gGameStarted, to our globalsModule. (Open it and choose "New Property" from the Edit menu.) Type in gGameStarted as boolean as the property value.

Now we just need to set gameWindow to make gGameStarted true when appropriate. We don't want it to be true unless the user is obviously playing the game, so we'll add gGameStarted = true at the top of the following routines:

  • cardKing
  • cardMatch
  • deckAdvance
  • discardMatch

So basically, if the user advances the Deck or makes any kind of match at all, we assume the game is started.

But once the user's finished with a game, we must reset gGameStarted to false, so add this line to the very bottom of endOfGame:

  
gGameStarted = false

Excellent work. Now we just need to check the state of gGameStarted before we call confirmDialog. So go back to app, open the fileNewGame handler, and make it look like this:

  
if gGameStarted then

// Confirm restart
gDialogReturn = ""
confirmDialog.showModal

if gDialogReturn <> "" then
gameWindow.endOfGame
newGame
end if
else
newGame
end if

This just makes it so we only throw up the confirmation dialog whenever the game has actually started. If the game hasn't started, we just start a new game immediately (effectively redealing the cards).

So run the program and test it. When you don't make any matches, you won't get a confirmation dialog when you hit Command-N. But when the game's started, it will ask before resetting. Hit Cancel in the confirmation dialog and nothing happens. Click Okay and you get a new shuffle. Awesome!

Well, we accomplished a few things today, but we'll finish up the polishing next week. Have courage: we're almost done! If you want the complete REALbasic project file for this week's tutorial (including resources), you may download it here.

Next Week

We continue with our wrap up lesson, polishing the program to make it shine.

Letters

This week we have a question on background pictures from Cap:

Dear Marc,

you got a very interesting way to load background pictures in your app.

I want to port this method of loading skins into another app (a new project of mine).

Let's say, this app consists of 10 different windows. By now, my app is able to upload pictures (getopenfolderitem) and to put a copy of them into a special folder. Now I used your method "LoadBGPictures" in every of these 10 windows.* in the open-event of these windows I put "window1.loadBGPicturex(gBackgroundpicturename1)" (once this was in the load prefs folder, but this took too much time at the project launch)

* the loadprefs-method looks like this:

  
.
.
.
select case gDirectives(i).name

case "bgpicture"
gBackgroundPictureName = gDirectives(i).text
case "bgpicture1"
gBackgroundPictureName1 = gDirectives(i).text
case "bgpicture2"
.
.
.

So what do these windows load, when they are opened? They are able to load pictures out of the folder, where I put the copies of the getopenfolderitem-pictures.

Unfortunately, this way of loading skins is not very fast.

Question 1: How do I make a copy of a picture (let's say it's a GIF) in PICT without icon resources (you know, the preview stuff and all those things that make pictures fat; compare this to the feature of GraphicConverter to make very small-kB pictures)

Question 2: Is this way of loading skins the best one, if I load skins for more than one window? Is there a "trick" to load pictures faster?

Thank you, Marc!

Greetings,

Cap

It sounds to me like speed is your main problem: you don't want a delay when the user changes the skin. With programming, most things are compromises. One way is faster but more complicated, or perhaps it uses more memory.

In your situation more memory might be the key: you could pre-load the pictures into the program so when the user chooses a different skin, it's already loaded and ready. Of course if you need different pictures for each window, and if you offer a number of skin styles, that could be a lot of pictures to remember!

The key when attempting to optimize (speed up) a routine is to know exactly what area is causing the slow down. Is it loading the pictures from the disk file? Or is it displaying the pictures in the window?

Once you know where the speed drag is, you can set about solving it. You mentioned once putting the picture loading routine in your prefs but it made the program take too long to launch. A solution to that might be to use a thread: we haven't talked about those in RBU yet, but threads allow code to execute without halting the other aspects of your program. They can be simple and very tricky, depending on your usage.

A simple thread works like this: you add a class to your project and give it a super of thread and name it threadClass. Drag it to your project window to instantiate it (RB will rename it threadClass1). Open it in the Code Editor and in the Run event, put your picture loading code.

For a test, put in this:

  
dim i as integer

for i = 1 to 1000000
i = i + 1
next

beep

Then, in the Open event of your program, you could do something like this:

  
threadClass1.run

What this will do is get the thread starting to execute. Any code in the thread will be run, while the rest of your program will continue. The user will still be able to do things. If you try this, you should hear a beep 3 or 4 seconds after you run the program.

The only thing to remember is you must be careful to never assume, elsewhere in your code, that the picture loading (or whatever the thread was to do) is finished. Always check variables for nil, or even create a global "gThreadDone" boolean so you can know that the thread finished and was successful.

It might also help to remember that speed is relative: if you can trick the user into thinking that something didn't take as long as it did, they'll think your program is fast. For instance, if you wait until a window is opened before loading the picture, the user might see a delay in the window's opening. But you can set the window's backdrop property before the window's displayed, only showing the window once everything is ready. (You might need to temporarily hide the window with window.visible = false until you're ready to window.show.)

I know this doesn't answer your question exactly, but I don't know enough about your specific project to advise you in anything more than general terms. (If I misunderstood something, feel free to send me a follow-up question.)

As to your first question, about saving a PICT without extra fluff, many graphics programs (like Adobe Photoshop) can do this. Photoshop can even save PICTs as JPEG compressed PICTs, which are considerably smaller than normal pictures (but they require QuickTime installed on the machine the program is running on to decompress). I'm not sure why you specifically require PICT graphics: REALbasic will accept JPEGs and GIFs dragged directly into a project window (or you can load those off disk just like PICT format files). If QuickTime is installed (and few Macs don't have at least some version of it), you can read in almost any format picture file.

Next, we have some words of gratitude from Frank:

Mr. Z.

thanks for the lessons. I have been reading "The Realbasic book" back to front a number of times and was very pleased to find your website with the projects.

Although I consider myself a very experienced IT professional (ex Cobol, Pascal, Basic etc programmer), working with an OOP language like RealBasic is a bit different. The projects help me to construct someting and I have so far been able to figure what actually happens. Although there are still some mysteries, I start to get a good feel for it. Bottomline is I would have given up on Realbasic if I hadn't found your lessons.

My billion dollar question is: How on earth do you plan/design (the technical design so to speak) an OOP software package, and how do you actually document it?

Regards

Frank

Thanks for the kind words, Frank. I can only say that my approach to teaching is based on my own experience, knowing how I struggled to learn all this stuff for years. I still feel like I know every little, which is probably good, lest I get a swelled head!

And your billion dollar question is indeed intriguing: I'm certainly no OOP expert and I'm often baffled by things or realize in retrospect that I could have used more OOP in an app and it would have been considerably easier, but I keep trying and learning, and that's what really counts. Documenting OOP is certainly tricky: objects, like real living beings, can do things on their own. That's what gives them power, but it also can make working with them challenging and intimidating. Sometimes they escape and you feel like you're trying to find a bunch of lost cats!


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.

.

.