Выбрать главу

Note that these values will have to be recalculated whenever the control is resized. For this reason, I set up a wm_sizechanged() message that calcall() would call whenever the client area of a window was modified.

The report-style list control was a little more complex. I first created two helper classes, gui_listbox_column and gui_listbox_item, which contained all of the information about a given item and column in the list.

gui_listbox_column is the simpler of the two classes. The main listbox class keeps, as a member variable, a dynamic array of gui_listbox_columns, which represent the columns in the listbox right now. gui_listbox_column contains all of the information needed for a column in our list box, including the name of the column, the alignment of the column, whether it’s shown or hidden, its size, etc.

The main listbox class also keeps a dynamic array of gui_listbox_items. The gui_listbox_item class contains everything related to a particular row (or item) in our report-style listbox. By far the most important data member of this class is the array of strings, representing the data for each column. I also decided to let each item store an additional 32-bits of data with it, via the m_itemdata member. This technique is similar to how Windows allows you to store 32-bits of data by calling SetItemData() and GetItemData() for your listbox items. This feature is important because it allows clients of the listbox to store a pointer with each item - usually a pointer to the specific class associated with the item, so that it’s readily available later.

As for drawing the columns and items… I decided that I’d like to have absolute control over how each individual item/column in the listbox was drawn. Towards this end, I decided to have the listbox draw its items and columns by repeatedly calling two virtual functions, gui_listbox_item::draw() and gui_listbox_column::draw(). Each function took one parameter - a rectangle understood to be the location on the screen where the column or item was supposed to be drawn. The default implementations of these draw() functions just spit out the text associated with that particular column and subitem in that rectangle; however, I could now easily derive and override draw() for items or columns that required a unique appearance. This technique has seemed to work for me so far, though I don’t claim that it’s the best or “right” way to do it.

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.

Saving Windows

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

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.