| |||||||||||||||||||||||||||||||||||||||||||||||
|
| |||||||||||||||||||||||||||||||||||||||||||||||
Print This Article REALbasic University: Column 041
RBU Pyramid XIVWelcome back, everyone! I trust everyone had a restful holiday and are geared up for an exciting new year. REALbasic University is back in session, and we're continuing with our RBU Pyramid project.
Adding a Help SystemWe're nearing the end and almost finished with our game. But a good game needs instructions: what if the user doesn't know the rules of Pyramid? So today we'll add an instruction window. For RBU Pyramid I wanted a simple help system. Nothing fancy, but competent, and above all, I wanted it easy to create. I could have used the standardize help system I use for my software programs, except that it's complex (it translates an HTML help file to styled text) and it didn't work under Windows (I've since completely rewritten that help system). The traditional help system I use is a two-paned window, similar to my Z-Write word processor. On the left is a listBox of topics, and on the right is the help text. When you click on a topic, it's displayed on the right. Elegant, but difficult to program, especially when you add in features like the ability to adjust the width of the listBox. But I did like the idea of multiple topics: a user doesn't like having to wade through a 10-page help file just to find out how to play the game. So one of the first thing I decided was instead of using a listBox-and-EditField combo, I'd use a popupmenu instead. The popupmenu would contain the topics and they'd be displayed in an editField underneath it. I quickly created a window that looked like this: ![]() You should be able to recreate this easily: that's a staticText in the upper left, a popupMenu to its right, and beneath them is an editField. That's it! The window has the following properties (settings): ![]() The editField should have these properties: ![]() The text property of the editField should be filled with the actual help text. I've already created this, so you can just put in this text file. For a help system like this, you have several choices with how to get the text into the program. The simplest is just to include the text in the program itself (by putting the text into an editField), which is exactly what we've done. Other choices include putting the text into the resource fork of the program (which wouldn't work for a Windows version of your program) or in an external file (which could be a program if that external file can't be located, i.e. the user moved or deleted it). In our case, the simplest solution is also the best for our needs. Our next problem is that we must divide our help text into separate topics. Once again, I went for the simplest solution: I decided that each section of text would begin with a headline in a particular format. I chose the following format:
That's three asterisks followed by a space, the topic name, another space, and three more asterisks. By searching the help text for text in that format, I can break the text into various sections and extract the topic names. (In computer programming lingo, this is called parsing.) As long as that particular heading text is unique (there aren't a series of asterisks in the body of the text), we're fine. The traditional method I'd use for this kind of system would be to set up an array data structure, with the text for each topic as separate string elements in the array. That, however, is more complicated than we need: why not just have program search the help text for the topic name and then scroll to it? That way there's only one piece of text and the topic menu just lets a user easily jump to an appropriate subject.
Coding Popupmenu1Let's try it! Open the code editor for InstructionsWindow (option-tab while the window is selected in your project list or double-click popupMenu1) and find the code for popupMenu1. Go to the Open event and put in this code:
What does this do? Well, in simple terms, it scans the help text for help topics and adds them to popupMenu1's menu. There's not much code here, so how does it accomplish this magic? Let's analyze it. We start out by initializing some variables: n is set to zero, s becomes our help text, and i is an integer representing the first occurrence of "*** " in the text (note the space after the three asterisks). Then we set up a while loop: while i is greater than zero we keep repeating the loop. Since i is set by the inStr function, which returns the character position of a search-for item within some text, it is set to zero when the search fails. So our while loop will therefore continue until there are no more occurrence of "*** " in the text! Next, we find the end of the line of the topic. We do that by searching for a return character (chr(13)) within the help text, but starting the search at position i. We save this information into the variable n. Then we use the mid function to grab the entire topic line. Since n is the end of the line and i is the start, the length (in characters) of the line is n - i. We pass that to mid, starting the selection at i, our start point, and grabbing n - 1 characters. The result is the topic: we pass that to popupMenu1's addRow method, which simply adds a menu item with our topic as the text. Finally, we reset i with a new search: it's still looking for "*** " but we start the search at i + 3. Note that that's past were we last stopped. If we started right at i, the search would immediately be successful as it would find the same topic again! (Our while loop would also never end -- the program would be stuck forever in an endless loop.) Finally, we set popupmenu1 so the first menu item is selected. Since all this happens when the window is first opened, from the user's perspective, the popupmenu simply has a list of help topics in it. But what happens when the user selects a menu topic? That's pretty simple. Go to popupMenu1's Change event and put in this:
The first thing we do is check to make sure that popupMenu1.listIndex does not equal -1. That's because -1 means that no menu item is selected. Any other value means the user has chosen an item on the menu, so we then simply search the text for that item. The variable i is set to starting position of the search-for text (me.text, which is the topic name on the menu), and j is the end of that text's line. Then we just highlight (select) that text by setting the selStart and selLength properties of editField1. Selecting the text effectively scrolls the EditField.
Remembering the Window PositionWe've got our basic help window, but we're not quite done. As usual, we want an elegant Macintosh program. That means a program that will do nice things like remember the location and size of the help window. That's extra work for us, but that's the Macintosh Way. As you'll see, this is very similar to what we did for HighScoreDialog. First, let's add two new global properties. Open globalsModule and add two properties (Edit menu, "New Property"): gHelpOpen as boolean and gHelpPos as string. The first will tell us if the help window is open or not, the second will contain details about the window's size and position. Like we did for HighScoreDialog, we need to prevent our window from saving window size changes when we resize it programmatically (we only want it to save when the user changes the window), so let's add a new boolean property to InstructionsWindow: opening as boolean. Now add the following to the window's Open event:
Here we're just setting our global gHelpOpen property to true, meaning that the help window is open. We set opening to true so we can adjust the window position ourselves, and then we change the window's size and position based on the info in gHelpPos. (The if countFields line is just an error check to make sure that gHelpPos contains good data.) Next, go to the Close event and put in this:
The first line simply says the window is closed, and the second calls a saveWindowInfo method (which we'll write in a moment). Now go to both the Resized and Moved events and put in this code in each:
This just says if we're not in the process of opening, save the new window size and position information. Now let's write that saveWindowInfo method. Add a new method (Edit menu, "New Method") and name it saveWindowInfo. Here's the code for it:
As you can see, this just saves the current window details into the string, separated by commas. Now that we've got this info saved, we need to remember for the next launch of the program. That means adding the info to our preference file. That's easy, of course. Let's open PrefModule and find the loadPrefs routine. Insert in this case statement (where doesn't matter, as long as it's before the end select line):
This will simply load gHelpPos with whatever help window info has been saved. The reverse, saving gHelpPos, is just as easy. Open savePrefs and put in this line before t.close:
Excellent! Note that unlike the score window, we don't bother saving the open/closed state of the help window. We're almost finished: we just need a way to make the help window appear.
Adding An Instructions MenuOpen the menu item within your project window. Go to the Options menu and click on the blank item at the bottom of the menu list. Type a hyphen ("-") and press Return. That inserts a separator line. Do that again, but type in "Instructions..." and press Return. While selecting the Instructions menu item, go to the Properties Palette and find the CommandKey property and type in an I. Your menu should look like this: ![]() Now open up the App class (within your project window): we need to do something with that menu command. Within the Events section, go to EnableMenuItems and insert this code (it doesn't matter where):
This will enable (ungray) the Instructions menu command. We change the text of the menu item depending on whether the help window is open or closed. But what about the command itself? Easy: go to the Edit menu and choose "New Menu Handler" and in the dialog that pops up, select "OptionsInstructions" from the popup menu and click okay. That should insert an OptionsInstructions handler within the Menu Handlers section. Go there and put in this code:
Simple: if the help window is open, it is closed, and vice versa. Guess what? We're done! At least for this week. RBU Pyramid is starting to look very polished, but there's still a few little things to go. We'll finish them up in a few more lessons. For now, give the program and whirl and check out the new help window! If you would like the complete REALbasic project file for this week's tutorial (including resources), you may download it here. Next WeekWe tackle the big bear: adding an undo system after the fact.
REALbasic NewsREAL Software announced this week the release of REALbasic 4.0. This new version adds some new features including significantly revamped ListBoxes and EditFields and an improved IDE. You can find about all about the latest release at realbasic.com. I'll have a column about these new features in a few weeks. REALbasic is available for US$149.95 for the Standard Edition and US$349.95 for the Professional Edition packages, direct from REAL Software. REAL Software offers academic and volume discounts as well as license-only options. Upgrades to REALbasic 4 range in price from $29.95 to $89.95 for owners of REALbasic 3.5.
LettersOur letter this week is from Remek, who has a -- gasp -- math question!
Okay, my palms are breaking out in a sweat here. Just the word "math" terrifies me and gives me nightmare flashbacks of high school algebra, but I'll do my best to figure this out and explain it in a way that even I can understand. First, your code above seems to have some troubles. If I'm understanding what you're wanting -- the product of all numbers between 1 and 1000 -- that's a problem that can be represented like this (with the asterix standing for multiply):
For that to work, you don't want to use x for both the for-next counter and as the result of your calculation as the calculation will change the value of x. You want something more like this:
Note that I've taken the liberty of rewriting your routine so it displays the results of each pass into a listBox. When you run this, you get this: ![]() As you can see, the calculation does start to go awry after the 33rd iteration. Why is that? To answer that, we need to learn a little about how computers handle numbers. You've probably heard of the binary system: ones and zeroes. Computers are always binary: everything is either a one or a zero. So how does a computer work with an 8 if there is no eight? Simple: it uses several ones and zeroes to represent an 8. So the number 8 in binary is "1000". Think about it, with a single digit, the highest binary possible is 1 (zero or one are the only two combinations). But with two digits (or bits, since bit means binary digit), you've got four combinations (00, 01, 10, and 11). That's the numbers 0-3! If you keep expanding this concept, more digits equals bigger numbers. Three digits eight combinations, four sixteen, five thirty-two. Wait a second, there's a pattern developing...
See how it works? The number of combinations is the number two raised to the power of the number of digits. So two to the power of 4 is sixteen, two to the power of eight is 256, and two to the power of 16 is 65536, and so on! Now there's a very simple relationship between the number of digits required to represent a number in binary and the maximum value that can be expressed by those digits. Remember how one bit (1 or 0) can only represent two values? Since zero is one of those values, the maximum number than can be expressed with a one bit number is one. Thus the max number that can be expressed is always one less than the number of combinations! So with three binary digits (bits), there are eight combinations and the maximum number that can be expressed is 7 (8 minus 1). For eight bits, the max number is 255. For sixteen, it's 65,535. What this means is that numbers, on a computer, are not infinite: there's always a maximum value that can be stored! You've heard terms like 8-bit, 16-bit, 32-bit, etc.? Well, that just represents the largest number that particular computer can work with! Since Macs are 32-bit computers, and REALbasic's number datatype is a 32-bit number, that's all we've got to work with. So how big a number can a 32-bit number represent? Let's see... two to the 32nd power is 4,294,967,296, so the biggest number is 4,294,967,295! However, there's another issue. Since numbers can also be negative, REALbasic limits integers to a range of plus/minus 2,147,483,648. That means the smallest number you can use is -2,147,483,648 and the largest is +2,147,483,648. Interestingly, 2,147,483,648 just happens to be exactly half of 4,294,967,296. Do you think that's a coincidence? Now take a quick look back at the screenshot of our program. See how it ran out of steam (numbers) around the 33 point? See how that number is very near our maximum number? Yup, once the number got to big, it became a zero! But what if you want to use bigger numbers? Is there nothing you can do? Well, REALbasic also has the double data type. I know we're used to using them to represent real numbers (also known as fractions or numbers with a decimal point). But doubles are 64-bit numbers. That's quite a bit bigger. According to REALbasic's documentation, a double can be any value between 2.2250738585072012 e-308 and 1.7976931348623157 e+308. Whew! Those are some big numbers! So if we make the simple change of making x a double instead of an integer:
And we run the program again, we get the following: ![]() Wow, that's much bigger! However, you can see we still run out of numbers before we even get to sixty iterations! Our little math problem just generates too huge numbers! Oh well. That's the best we can do, so we'll have to live with that. (There are some clever ways around that, but they're beyond the scope of my extremely limited mathematical skills.) I hope this little lesson on numbers has helped people: it certainly took me a few years to understand it all! I remember in high school, when I started programming, having to actually go to ask a teacher why a game I was programming wouldn't store the large sums of money I required. It turned out that since my BASIC used 16-bit numbers, 65,535 was the largest value I could use. I didn't really understand the teacher's explanation until years later. But since my game dealt with dollar values in the millions (it was a financial strategy game like Monopoly but with more money), my workaround was to simply make $1,000 the smallest amount of money and add three zeros to whatever number was stored (so 65,535 was really $65.5 million). That worked! BTW, if you'd like the source for the program above, you can get it here. 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.
| |||||||||||||||||||||||||||||||||||||||||||||||