Drawing the items required a little more work than the columns, however. Items had to be drawn with or without a highlight, depending on whether they were selected or not. Not a big deal, but important to remember.
Then there’s the issue of scrollbars. My report-view listbox contained two members, m_horzscrollbar and m_vertscrollbar, both GUI scrollbars. Whenever the size of the listbox was changed (wm_sizechanged()), it took a peek at the width and height of the data it had, and either displayed or hid the scrollbars as appropriate.
It’s been a whirlwind tour, but hopefully you have a general idea of what lies ahead of you in your quest to create controls for your GUI. The only thing I want to reiterate is “style is fun.” Don’t be afraid to take a few liberties as you create your GUI - implement the stuff you’ve always wished for, and the stuff that makes the most sense in your game. This is especially important if the game your making relies heavily on the functionality of your GUI - like, say, you’re making a RTS game.
But also remember that in creating your controls, your doing the same balancing act as with the rest of your game - you’re weighing features against development time. Give your players as easy and intuitive GUI as possible, but also, don’t spend all your time making 50 different controls. You want to strike a balance between functionality, the good thing, and complexity, the bad thing.
Part IV: Resource Editors and Other Madness
This section will address a whole bunch of miscellaneous issues and ideas to polish off your game GUI.
Window serialization (or, saving and loading windows) may or may not be crucial for your project. If your game GUI is minimal, you might be able to get by with just hard-coding the windows into your game. But if your GUI’s more complex than that, you’re going to want code that will save a window (and all its children) to a file, and then load it back up again. For starters, having window serialization code allows you to change your game’s GUI without recompiling, and is a boon if you’re working with more than one person. So, let’s spend a little time talking about how I implemented saving windows.
My plan of attack was easy - start at the main dialog window, and recursively go through all of its child windows, saving each one to disk. If I were programming in C, the first thing I would have said to myself would have been “OK, so if I have to save these windows, I need a byte for each window that tells me what type of window it is, so I can load it back up correctly. 1 is a button, 2 is a listbox, 3 is an icon, etc.”
This kind of problem is specifically what C++’s RTTI (Run Time Type Identification) addresses. RTTI provides two things, a type_info class and a typeid() function, which together allowed me to query an object for it’s class name - “ gui_window”, “gui_button”, etc. Instead of fiddling with enums and IDs, I simply call typid() for each window I’m going to save, and “write down” the class name of the window.
I saw two minor disadvantages to using RTTI’s object identification functions to help save windows. First of all, the RTTI IDs are strings, not integers, which means they’ll take up more space on disk (store them Pascal style, that is, the first 4 bytes describe the length of the string, followed by the string data itself). Second, if you change the name of one of your window classes, you’ll break any window files that you’ve previously saved. For these reasons, you might opt out of using RTTI in this manner - after all, just because a technology is there doesn’t mean you have to use it. However, I found RTTI to be a lifesaver in my code.
For more information on RTTI and these two functions, search for them in your online help. Also, if you decide to use RTTI with Visual C++, make sure you turn it on in your project settings, C/C++ tab, C++ language option.
Loading windows is more difficult than saving them, primarily because you have to new each window, load it up, and then remember to delete it when it’s no longer needed.
This function is recursive, and looks like this in PDL:
void gui_window::load(int filehandle) {
// read window properties (colorsets, etc.)
// read total number of children for this window
// for each child…
// read window ID from disk
// new a gui_window derivative based on that ID
// tell the newly created window to load itself (recurse)
// next child
}
In other words, you’d load windows from disk exactly as you would expect. First, you take care of the base window: read in its properties. Then, read in the total number of children of the base window. For each child, read an ID byte, new up a window based on that ID, and then tell that new window to load itself (recurse down into it). Once all of your children are loaded, you’re done.
Of course, it’s also very important that your file structure mirrors this same layout. Make sure your save code saves things in the same order that you’re loading them.
To really make your game GUI shine, you’re going to need a resource editor. Certainly you don’t need one as slick and functional as Developer Studio’s, but you at least need a basic application that will let you add, edit, delete, and arrange things, and will save you the trouble of calculating out virtual coordinate positions for all of the controls in your dialogs.
Writing a full-featured, WYSIWYG resource editor is beyond the scope of this article, but I can give you a few miscellaneous tips for if you do decide to attempt such a beast:
· Share your code.Specifically, make the resource editor share the same rendering code as your actual game. This way, you get WYSIWYG support, and you save yourself the trouble of having to implement two sets of GUI code. I guarantee you, it’s easier to tweak your DirectX code so that it renders to a GDI surface instead of to a double-buffered system than it is to re-implement an entirely new drawing core. Remember also that it’s quite likely your GUI system will change over time - you don’t want to constantly have to change code in two different places.
· Don’t try to emulate DevStudio’s look and feel.In other words, don’t waste time trying to figure out how to mimic the various features of the DevStudio GUI (like, say, tabbed property sheets and preview windows). Don’t feel bad if your Resource Editor is ugly in comparison; yes, it’s true that the productivity of a team is directly proportional to how useful its tools are, but at the same time, it’s very unlikely that anyone outside your team will be using your Resource Editor, and you’re not using it to create a full-fledged GUI app; you’re just making a few dialogs. You don’t need context sensitive help. You don’t need context menus, unless in your opinion they ease a particularly tedious operation. It’s OK if your resource editor isn’t polished, just so long as it gets the job done.
· Emphasize data integrity over speed.The Resource Editor is a data wrangler, not a high-performance app, and there’s nothing more annoying than when a dialog you’ve spent an hour designing goes down the tubes because of a program crash. When writing your GUI, preserving data should be your highest goal - implement autosaves, undo buffers, etc, and go easy on the optimization.
Those of you who are familiar with the way Win32 does its windowing probably already know what the term “subclass” means. For those of you who don’t - when you “subclass” a window, you effectively “derive” a new window type, and then wedge your new window type into places where the old window used to be.
Читать дальше