Using a CallHandler and Caller Object to Simplify Function Calling Between Anonymous Objects
Graphical User Interface-bits (controls, widgets, whatever) are a natural for object-oriented programming. Dialog boxes are containers of controls. Controls can have their draw or mouse-click behaviors overridden for various custom behaviors. If there's something in the world that seems like a natural for object-orientation, it's GUI controls. A problem, though, comes when you have a specialized behavior that is too trivial to deserve its own object. Suppose for example that you are building a scrolling listbox object. You want a couple of buttons in the corner of the box that will scroll the contents. If you build a straight set of GUI objects that require special behaviors be taken care of via inheritance, you'll need to derive some kind of "listbox-scrollbutton" class that has the ability to instruct the listbox to scroll its contents. While it is an object-oriented solution to the problem, it's also an unruly solution to the problem. Using the standard approach, you'll find yourself building plenty of trivial GUI objects that do almost nothing. For example, let's take a look at some standard GUI objects. Suppose we have three objects, Control, Button, and ListBox. These objects are greatly simplified for illustrative purposes, so don't think this is anything even close to a complete GUI here. class Control // abstract base class for controls { public: Control(DialogBox &rParent, const Rectangle &rR); virtualvoid MouseButtonDown(const Point &rPos) { } // called by the control when someone clicks in the control's rect protected: virtual ~Control(); }; class Button : public Control { public: Button(DialogBox &rParent, const Rectangle &rR); virtual ~Button(); virtual void MouseButtonDown(const Point &rPos); }; void Button::MouseButtonDown(const Point &rPos) { // draw the button as down, etc. } Simple enough. Now let's make a listbox, and we'll give it a couple of buttons in the corners to scroll the contents up and down.
It looks great, but we've got a small problem. What we would like to happen would be for the Button objects to call a function in the ListBox when they've been clicked so the ListBox can scroll itself up or down accordingly. With our current scheme outlined above, we'd have to do this: class ListBoxScrollButton : public Button { public: ListBoxScrollButton (DialogBox &rParent, const Rectangle &rR, ListBox &rBoxToScroll, const bool &rUp); virtual ~ListBoxScrollButton(); virtual void MouseButtonDown(const Point &rPos); private: ListBox &rMyBox; // the "owner" box to be scrolled bool Up; // am I the "up" button or the "down" button? }; void ListBoxScrollButton::MouseButtonDown(const Point &rPos) { Button::MouseButtonDown(rPos); // show the button as down from the parent class if (Up) rMyBox.ScrollUp(); else rMyBox.ScrollDown(); } Then the ListBox, instead of containing two Button objects, would contain ListBoxScrollButton objects that have the specialized task of telling the ListBox to scroll itself. While this does work, it's a bit of a pain. You need to implement a new class derived from Button just to do a little if-then statement and call a function in ListBox. Also, it makes your design more of an object-sprawl, as you're now adding ListBox-specific functionality to objects that aren't the ListBox. A solution to this problem is to make an interface in objects to allow them to call functions in anonymous objects. This can be done with function pointers, but there are a couple of stumbling blocks with that approach:
The two small classes presented below handle these problems. The meat of it is a Caller class that stores object instances and function pointers so your object doesn't have to know about the object in which it's going to call a function. It's got a standard interface so you can pass info to and from the object being called. Finally, it cleans up the function pointer interface a bit with a macro that does the casting for you. The interface consists of three items total:
Here's the code. There's not all that much of it.
There's no real magic to the CallHandler code. It's been implemented similarly in a dozen different projects. This just happens to be my favorite way of doing it, because it can preserve the object-oriented nature of your code, and it is very lightweight. I've seen more elaborate versions that either use the underlying system's message queue, implement their own asychronous message queue, or use "functors" (C++ objects that look like functions). While they all work, this method seems to have the best balance between size and portability. Let's look at how a CallHandler can help out our problem with building specialized objects for the ListBox object. First, let's change our Button object a bit: class Button : public Control { public: Button(DialogBox &rParent, const Rectangle &rR); virtual ~Button(); virtual void MouseButtonDown(const Point &rPos); void SetMouseButtonDownCaller(const Caller &rC) { MyMouseButtonDownCaller = rC; } private: Caller MyMouseButtonDownCaller; }; void Button::MouseButtonDownHdl(const Point &rPos) { // draw the button down, etc. MyMouseButtonDownCaller.Call(this); // call the handler if another object has set one } Now our Button object contains a click handler in addition to its existing virtual MouseButtonDown() function. If you need to handle a click, you now have the choice of deriving from Button as we originally did or overloading the button's click handler. The choice of which to do depends on the job you're planning. If you just need a function to be called when the button is clicked, as we do in this example, use the handler. If you need to override several behaviors, it's probably better to derive a new object from Button. Don't worry if you don't need to set a click handler. The Caller object by default sets its members to NULL, and if you do invoke the Call() member on a Caller object that has not been overridden, it will just return without doing anything. In any case, we no longer need to make a special object derived from Button. We can go back to our original ListBox object, but with a couple of lines of code to tell the Button object that we want to be called. class ListBox : public Control, CallHandler // note that we're now derived from CallHandler { public: ListBox(DialogBox &rParent, const Rectangle &rR); virtual ~ListBox(); virtual void MouseButtonDownHdl(const Point &rPos); int ButtonHandler(Button *pButton); // this will be called by the buttons protected: Button ScrollUpButton, ScrollDownButton; // we can just use standard button objects now }; ListBox::ListBox(DialogBox &rParent, const Rectangle &rR) { // do the standard member-initialization here ScrollUpButton.SetMouseButtonDownCaller(MakeCaller(this, ListBox, ButtonHandler)); ScrollDownButton.SetMouseButtonDownCaller(MakeCaller(this, ListBox, ButtonHandler)); } void ListBox::MouseButtonDownHdl(const Point &rPos) { // change the selected item in the listbox } int ListBox::ButtonHandler(Button *pButton) { if (pButton == &ScrollUpButton) { // the up button was clicked, scroll the list up } else { // the down button was clicked, scroll the list down } return 0; // you can use this to return info to the button } Now a special ListBoxScrollButton object that handles the clicks is not necessary. In the constructor, we told the Button object that when it is clicked it should call the ButtonHandler function. The ButtonHandler checks to see which button is clicked (done for brevity's sake. We could have made two functions if we wanted), then does the appropriate scrolling. It's a fairly simple and portable way to keep from polluting your namespace with dozens of little objects that do trivial things in dialog boxes and elsewhere. In short, this is one of my favorite hacks. It's simple and can make your programs simpler. It's certainly not limited to writing GUI's, but that's a place where the need for such a thing crops up repeatedly. Discuss this article in the forums
See Also: © 1999-2011 Gamedev.net. All rights reserved. Terms of Use Privacy Policy
|