Polymorphism in a C++ GUI

Started by
5 comments, last by sirGustav 14 years, 7 months ago
Looking at C++ GUI's I'm familiar with like MFC and CEGUI all of them seem to need heavy use of downcasting. One example would be casting down from a top level Window or CWnd object returned from a method like GetDlgItem. The reason for this is obvious, every window has to store a list of child windows which is ideally constructed at runtime based on some resource file, an XML file in the case of CEGUI for example. To store these objects together you need to throw away information about what each one actually is, say a button or edit box or whatever. So you can do as much as you can by making the widgets autonomous and eal with specific behaviour by passing event handler ID's back up to the parent dialog to deal with, on button presses for example. But I find there's only so far you can get with this and sooner or later you need to manipulate a window directly by getting a pointer to it, e.g to reset a check box based on a input elsewhere in the app. Another example is in event handling where you find yourself casting a top level EventArgs object to MouseEventArgs or KeyEventArgs, or in the case of MFC handling a message that takes an LPARAM and casting it a pointer of the type you think it should be. Given that downcasting is normally regarded as bad practice is there a better way of designing GUI's and event systems that avoids it in C++?
Advertisement
Quote:Original post by Somnia
... is there a better way of designing GUI's and event systems that avoids it in C++?

Yes basically you can use the double dispatch pattern. Here is one example and another example but it does have it down side, mainly that your base interface has to know about all the types of events/messages/widgets.
Thanks that looks interesting.

I suppose it could work with a "DoDataExchange" type thing that MFC uses. This is where you have effectively two copies of a control variable, one with type information defined in the derived dialog class and the actual one, and you call the DoDataExchange method to copy data from one to the other to keep the two in sync.

I can't quite decide if this is worth it compared with the enumeration + downcasting method. Double dispatch seems quite elegant, but it's possible overkill for a bit of extra type-safety. Either way your base class needs to know about all of its possible child classes.
Don't copy MFC style. It's one of the most ugliest code written in C++, ever. Take a look at other GUI libraries, like Qt. They make heavy use of polymorphism, but you don't have to deal with downcasting directly.
As with signal&slots, you don't have that horrible messaging stuff, although the additional preprocessing step is tedious (in my opinion), but can be prevented by using the sigslot library or boost::signals.
MFC is ugly because it was written a long time before Microsoft got around to implementing all the C++ features to make it cleaner. (In their defence, the standard was only ratified in 1998.)

Having said that, you will find that you simply can't solve every single widget's problems through a standard interface. A spin control needs different logic to an edit box, for example. Attempting to abstract that away too much will lead to code that is far more convoluted than a simple downcast. Ultimately you could conduct all your reads and writes via a generic messaging system and let the messaging system ensure everything only goes to legitimate sources, but you end up obscuring the GUI operation far too much that way.

An alternative is just to ensure that you always get something safe from a downcaast, perhaps by checking the type (via RTTI or similar) and raising an exception when it's the wrong type, that sort of thing.
Looking at more modern systems it seems they tend to work by getting the visual forms editor to actually write your source code for you, but that's sounds like overkill for my simple game GUI.

Quote:Original post by Kylotan


Having said that, you will find that you simply can't solve every single widget's problems through a standard interface. A spin control needs different logic to an edit box, for example. Attempting to abstract that away too much will lead to code that is far more convoluted than a simple downcast. Ultimately you could conduct all your reads and writes via a generic messaging system and let the messaging system ensure everything only goes to legitimate sources, but you end up obscuring the GUI operation far too much that way.

An alternative is just to ensure that you always get something safe from a downcaast, perhaps by checking the type (via RTTI or similar) and raising an exception when it's the wrong type, that sort of thing.


Yes, I'm coming around to this conclusion. I've thought a few times that I don't really see that there shouldn't be a place for RTTI, if your in a case like this one where you really need to create your types dynamically and the later you really need to know what they are, as long as mismatch errors are caught easily.

Create the widgets yourself.
While it may sound like every "modern" system that generates code, bear with me :)

The basic gui that you are describing works something like this:
Gui* gui = CreateGui("file");Button* btn = (Button*) gui->getWidget(button_id);

How about placing the button-creation-code between the load-call and the creation of the other (unused) widgets and placement and layout of everything? Sort of like this:
Gui* gui = CreateGui("file");Button* btn = CreateButton(gui, button_id);gui->createAndLayoutWidgets();

The CreateButton() function essentially finds the button_id, tries to create a button for it and store the base widget/object within the gui. The createAndLayoutWidgets() method then only creates a widget if it wasn't previously created and does the rest of CreateGui() as normal.

This topic is closed to new replies.

Advertisement