| |||||||||||||||||||||||||||||
|
| |||||||||||||||||||||||||||||
Print This Article REALbasic University: Column 037
RBU Pyramid XI hope everyone had an enjoyable Thanksgiving holiday. Now it's back to REALbasic programming! This week we're going to add a fun feature to RBU Pyramid: the ability to specify a background pattern for the game's main window. This will involve drawing graphics, adding an option to our preference file, and using the contextual menu control.
Adding New ObjectsWe'll begin with the creation of a background canvas. Drag a new canvas object to your project window. Give it the following settings (properties): ![]() If bgCanvas is covering the other objects in your window, with it selected, go to the Format menu and choose the "Move to Back" command. That should fix things. Next, let's add a contextual menu: it's the tool that looks like this: ![]() Drag it to your window and stick it anywhere. (Since it's not visible to the end-user, it doesn't matter where you put it.) Rename it to bgContextualMenu. Now, before we get to doing some stuff with these controls, let's add some global properties we're going to need. First, we need a global picture property that will hold the background picture. We'll also need a string variable to remember the picture's name, and we'll create a constant that will be the name of the folder where the patterns will be stored. So open globalsModule and add the following properties (Edit menu, "New Property"):
To add the constant, go to the Edit menu and choose "New Constant" and put in kBGPicts for the name and Backgrounds for the value. Good. Now we're all set for some coding!
Drawing the PatternClose globalsModule and open the bgCanvas code editor. Go to the Paint event and put in this code:
Looks simple, doesn't it? All this does is draw the pattern a bunch of times -- enough to fill the entire bgCanvas. First, we make sure gBackgroundPicture isn't nil. If it is, we don't draw anything. (Remember, nil means it doesn't exist. If you try to draw a nil graphic, your program would crash.) The actual drawing is simple. We figure out how many graphics it will take to fill the window in each direction (horizontal and vertical) and we store that calculation in nX and nY. Then we set up two for-next loops that repeatedly draws the graphic. Each time through the loop, the graphic is drawn one graphic further over (or down). The result, if the graphic is a proper pattern, is a seamless background. Moving on, let's go bgCanvas' MouseDown event. Here is where we check to see if the user's asking for a contextual menu (on the Mac this is a Control-click; on the PC, this is a right-button click).
Pretty simple, isn't it? We call the IsCMMClick function and open bgContextualMenu if the result is true. If the user has clicked the mouse button on bgCanvas without holding down the Control key (i.e. it's not a contextual menu click), we do nothing.
Handling the Contextual MenuIn the past, we've gotten our graphics into our project by dragging them into our REALbasic project window. That's great for permanent graphics that we don't want the user to change. But in the case of background patterns, we want the user to have the ability to add their own graphics. So instead of embedding our graphics, we'll keep them in a separate folder next to our application. Whatever graphics are in that folder will be available for the user to select within RBU Pyramid. To give the user an interface to those files, we'll use a simple contextual menu: it's a menu that pops up when the user clicks the mouse button while holding down the Control key (on a Mac, at least). The contextual menu is told to display itself by calling it's open method. What happens when we tell bgContextualMenu to open? Well, what we want to happen is have it display a list of graphic files within the "Backgrounds" folder. Since that list is dynamic (always changing), we can't prebuild the list: we must build it when the user clicks. Here's how we do that:
It's not as complicated as it seems. The first few lines are simple: we are adding rows (menu items) to our popup contextual menu. We first add a "None" option and then a separator. After that, our goal is to add the pictures in the "Backgrounds" folder.
Once we've got a valid folderItem pointing to the folder containing our "Backgrounds" folder, we then look for its "child" (an item inside it) named "Backgrounds" (we use the constant kBGPicts instead of hard-coding the real name. Next, we check to make sure our folder pointer is valid (not nil). If it is, we loop through every item in that folder and check to see if it's a picture file. (Who knows? The user could have thrown some text files in there!) How do we know if it's a valid picture file or not? Simple: we try to open it as a picture. If there's an error, our picture property, p, will be set to nil. If the file is a valid picture, we add the name to our contextual menu. All this sounds like a lot, I know, but remember, it happens in a split second. The user shouldn't even notice a delay. (I suppose, with hundreds of files in the "Backgrounds" folder and a slow computer, it might take a few seconds for the contextual menu to display, but that's an unlikely scenario. For our purposes, this method works fine.) Once we've got the menu built, it's displayed to the user and the user can select an item from that menu. What happens then is whatever we tell the Action event to do. Go there and put in this:
Yes, all we do is pass this event to a method: nothing really happens here. Why use a separate method? It's often cleaner and it helps if you need to call the same routine from several places. For instance, we're calling this now from within a contextual menu, but what if there was a regular menubar menu item that called the same routine? Separating the code into a method makes it easier to reuse that routine. So let's add a new method (Edit menu, "New Method"). Give it these settings: ![]() Here's the code for the loadBGpicture method:
First we see if the user chose the "None" option. If they did, we set gBackgroundPicture to nil and redraw bgCanvas (if there was already a picture there, it now goes away). Then we do a return command, which stops executing this method. If the user selected a file name, we attempt to open it as a picture. If the picture's good (not nil), we then assign it to gBackgroundPicture. We put the picture's name into gBackgroundPictureName. Then we redraw bgCanvas (by telling it to refresh itself). That's it! Your program should compile and run now, though if you don't have a "Backgrounds" folder in the same folder as your project, it won't let you choose a background graphic. (If you downloaded the compiled RBU Pyramid beta, you can copy the "Backgrounds" folder from it, or get it from today's project file.) With a valid "Backgrounds" folder, your version of RBU Pyramid should let you choose various background patterns. There's only one problem: when you quit Pyramid, that setting isn't saved. Let's fix that!
Adding a Preference SettingRemember when I explained my preference file system I said that it was easy to add/remove preference options? Well, now's our chance for you to see how easy it really is. Open prefModule and go to the loadPrefs routine. Somewhere within the select case routine, add the following case:
It doesn't matter where you put this (first or last), since the code will only be executed when the pref setting matches the name "bgpicture". The code simply stores the picture name into our gBackgroundPictureName variable (were you wondering what it was for?) and then tells loadBGpicture to load the background picture passed. (See? Making loadBGpicture a separate routine was important!) Now go to savePrefs and do the reverse. This time, we save the picture name (nothing if no background picture is being used).
That's it! That wasn't too hard, was it? Run the program, select a background graphic, then quit it. When you relaunch it, the same background should be visible. Cool, eh? (Extra credit: open the RBU Pyramid Preference file in a text editor and see what's in it!) It should look something like this: ![]() If you would like the complete REALbasic project file for this week's tutorial (including resources), you may download it here.
Next WeekWe've been focusing on jazzy user-interface features for a bit, so in our next lesson we'll get back to actual game logic and fix a few issues to make it play better.
LettersRachel Msetfi writes with a simple but common problem:
Easily done, Rachel! The best way is to store the contents of an editField in a string variable (a string variable can contain virtually unlimited text). That allows you to "concatenate" -- join -- multiple strings (the contents of several editFields) together. For example, let's assume you've got a save routine (a method named saveStuff). Inside this routine, you'll need to join the editFields together and save the results into a file. The first question: where is the file? Working with files in REALbasic can be a little tricky until you understand the logic behind them. Remember, RB has essentially two types of file objects. The main one is a folderItem, which is a pointer to a specific file (either one that exists or one that may exist). Once you have a folderItem object, you can use its methods to create either a text or binary file: that's the second file object you'll need. It will either be of type textOutputStream or binaryStream (depending on the kind of file you want to create). In your case, we'll use a textOutputStream object. For your situation, we'll assume the reader can choose a filename and location to save the file (thus we'll use RB's getSaveFolderItem function). The second part of your task is to decide how you will divide the different fields of data. For example, if you had two editFields, one with a person's name and another with a person's phone number, and you wrote that information to a text file as a single piece data, you'd end up with a file containing: Steve Jobs408-555-1212 From that data, how would you figure out which is the name and which is the phone number? It's easy for a human to tell, but much more difficult for a computer. But if you separated the two pieces of data with a unique character (such as an asterix [*] or an unprintable carriage return or tab), it would be easy for you to know where one field stops and the next starts. With those two questions answered, we can write our routine. Assuming we have the following editFields: ![]() Our saveButton action routine will contain the following code:
The comments within the above code should explain things fairly well, and hopefully this example should get you started. You can download the full sample project here. Keep those letters coming! I may not answer your question immediately, but I'll hopefully get around to it in a future column. (If you don't want your correspondence published, just be sure to indicate that when you write. Otherwise I'll assume it's fair game.) 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.
|
. |
| |||||||||||||||||||||||||||