Now that we’ve got scrollbars, we can tackle the most complex control of them all… the listbox.
Resign yourself to this now - the listbox control is where you’re going to be spending the most time.
// represents a column in our listbox
class gui_listbox_column {
public:
gui_listbox_column() {}
virtual ~gui_listbox_column() {}
virtual void draw(uti_rectangle& where);
void setname(const char *name) { m_name = name; }
uti_string getname(void) { return(m_name); }
int getwidth(void) { return(m_width); }
void setwidth(int w) { m_width = w; }
private:
uti_string m_name;
int m_width;
};
// an item in our listbox
class gui_listbox_item {
public:
gui_listbox_item() { m_isselected = 0; m_indent = 0; }
virtual ~gui_listbox_item() {}
virtual drawitem(int colnum, uti_rectangle& where);
void clearallcolumns(void); // boring
void setindent(int i) { m_indent = i; }
int getindent(void) { return(m_indent); }
void settext(int colnum, const char *text); // boring
uti_string gettext(int colnum = 0); // boring
void setitemdata(UL itemdata) { m_itemdata = itemdata; }
UL getitemdata(void) { return(m_itemdata); }
void setselected(int s = 1) { m_isselected = s; }
int getselected(void) { return(m_isselected); }
private:
int m_isselected;
int m_indent; // # of pixels to indent this item
UL m_itemdata;
uti_pointerarray m_coltext;
};
// the listbox itself
class gui_fancylistbox: public gui_window {
public:
gui_fancylistbox() { m_multiselect = 0; }
virtual ~gui_fancylistbox() { clear(); }
int getselected(int iter = 0);
virtual int wm_command(gui_window *win, int cmd, int param);
virtual int wm_paint(coord x, coord y);
virtual int wm_lbuttondown(coord x, coord y);
gui_fancyscrollbar_horz& gethscroll(void) { return(m_hscroll); }
gui_fancyscrollbar_vert& getvscroll(void) { return(m_vscroll); }
virtual int wm_sizechanged(void); // the window's size has changed somehow
gui_listbox_item *getitemat(int index); // boring
gui_listbox_item *additem(const char *text); // boring
int delitem(int index); // boring
int delallitems(void); // boring
gui_listbox_column *getcolumn(int index); // boring
int addcolumn(const char *name, int width); // boring
gui_listbox_column *getcolumnat(int index); // boring
int delcolumn(int index); // boring
int delallcolumns(void); // boring
int clear(void); // delete columns & items
int getnumitems(void);
int getnumcols(void);
void deselectall(void);
void selectitem(int item);
void selecttoggleitem(int item);
void deselitem(int item);
private:
int m_numdispobjsy;
int m_vertgutterwidth; // # of pixels between items vertically
gui_fancyscrollbar_horz m_hscroll;
gui_fancyscrollbar_vert m_vscroll;
bool m_multiselect; // is this multi-selectable?
uti_pointerarray m_items; // array of gui_listbox_items
uti_pointerarray m_columns; // array of gui_listbox_columns
};
The listbox is by far the most complex control you’ll make… but that’s only because it’s the most versatile. A good listbox control, capable of multiple columns, indenting, and multi-selection will prove practically indispensable in your game’s GUI. Stop and think for a moment about all the places that listboxes are used in the average game, and you’ll quickly see my point.
I tackled my listbox control by splitting it up into two separate controls: a multi-column “report-style” list control, and an icon list control, which creates a view similar to what you’d see when selecting “large icons” in an explorer window.
The icon list control was fairly easy to do. It kept track of a list of static icons (again, note the code reuse), all the same size. I divided the listbox width by the width of the icons, which gave me the number of columns available. (If it turned out that my listbox was smaller than the largest icon, I assume I have only one column, and let the gui system take care of clipping the icons so that they don’t overrun my client area). Once I had the number of columns, I calculated how many rows I’d need by dividing the total number of icons by the number of columns. This told me how to setup my included scrollbar (again - complex controls as combinations of simple ones).
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.
Читать дальше