Multiple Inheritance
Source Code Listings
This page has the following complete source code listings:
Listing 1: Mix-in class with wxWidgets RTTI example
Listing 2: Mix-in class with event table example
Listing 3: Virtual base class example
Listing 4: Non-virtual base class example
Introduction
This page details ways to use multiple inheritance with wxWidgets.
Multiple inheritance is discouraged but here are a few hacks that will help you to "play with fire".
At first glance, wxWidgets seems to encourage multiple inheritance. The IMPLEMENT_CLASS2() macro takes arguments for two base classes and, even though it is not used in wxWidgets source code yet, is ready for use. The wxScrollHelper, wxClientDataContainer, wxOwnerDrawn and wxRadioBoxBase classes are all built-in wxWidgets mix-in classes that both internal wxWidgets classes and user classes use by inheriting from multiple classes.
However, once you try to use multiple inheritance with your own classes, problems surface. Multiple inheritance from a common ancestor base class, whether or not you use virtual base classes, causes compiler errors such as:
error C2594: 'type cast' : ambiguous conversions from 'void (__thiscall MyFrame::*)(class wxCommandEvent &)' to 'void (__thiscall wxEvtHandler::*)(class wxCommandEvent &)'
Creating mix-in classes without a base class doesn't work because the IMPLEMENT_CLASS(), IMPLEMENT_CLASS2() and BEGIN_EVENT_TABLE() macros all require base class names as arguments.
Furthermore, once you work around these problems, the wxDynamicCast() macro is incompatible with mix-in classes and can return corrupted pointers.
This page explains all the wxWidgets problems with multiple inheritance and presents additional source code to use in your programs to fix or workaround these problems.
How to put a mix-in class without a base class into wxWidget's RTTI
Consider the following code:
class MyMixInClass { public: MyMixInClass() { } virtual ~MyMixInClass() { } virtual bool DoSomething() { wxMessageBox("In MyMixInClass::DoSomething()"); } DECLARE_CLASS(MyMixInClass); };
This class compiles but it does not link. For those familiar with wxWidgets RTTI, the reason is obvious: there is no call to IMPLEMENT_CLASS() which actually implements the RTTI code for MyMixInClass.
Unfortunately, there are only two flavors of this macro: IMPLEMENT_CLASS() and IMPLEMENT_CLASS2(). IMPLEMENT_CLASS() requires two arguments: the class name and a base class name. IMPLEMENT_CLASS2() requires three arguments: the class name and two base class names. Since MyMixInClass has no base classes, these macros cannot be used.
To implement MyMixInClass, a new macro called IMPLEMENT_CLASS0() is needed. IMPLEMENT_CLASS0() only takes one argument: the class name. This macro implements the wxWidgets RTTI system for a class that has no base classes.
The code for IMPLEMENT_CLASS0() is:
#define IMPLEMENT_CLASS0(name) \ wxClassInfo name::sm_class##name(wxT(#name), 0, 0, (int) sizeof(name), (wxObjectConstructorFn) 0);
The code for IMPLEMENT_CLASS0() wxWidgets version 2.6.3 is:
#define IMPLEMENT_CLASS0(name) wxClassInfo name::ms_classInfo(wxT(#name), NULL, NULL, (int) sizeof(name), \ (wxObjectConstructorFn) NULL); \ wxClassInfo *name::GetClassInfo() const { return &name::ms_classInfo; }
To use the macro for MyMixInClass is easy. The code is:
IMPLEMENT_CLASS0(MyMixInClass)
The previous code is all that is needed to add MyMixInClass to the wxWidgets RTTI system.
The full source code for an example that uses these techniques is:
Listing 1: Mix-in class with wxWidgets RTTI example
#define IMPLEMENT_CLASS0(name) \ wxClassInfo name::sm_class##name(wxT(#name), 0, 0, (int) sizeof(name), (wxObjectConstructorFn) 0); class MyMixInClass { public: MyMixInClass() { } virtual ~MyMixInClass() { } virtual void DoSomething() { wxMessageBox(wxT("In MyMixInClass::DoSomething()")); } DECLARE_CLASS(MyMixInClass); }; IMPLEMENT_CLASS0(MyMixInClass) class MyFrame: public wxFrame, public MyMixInClass { public: MyFrame(wxWindow * parent): wxFrame(parent, wxID_ANY, wxT("Demo"), wxDefaultPosition, wxSize(200, 200)) { wxMenu * pMenu = new wxMenu(); pMenu->Append(wxID_HELP, wxT("Help")); wxMenuBar * pMenuBar = new wxMenuBar; pMenuBar->Append(pMenu, wxT("&File")); SetMenuBar(pMenuBar); Show(); } virtual ~MyFrame() { } void OnHelp(wxCommandEvent& event) { // verify that RTTI considers MyFrame to be of MyMixInClass type if (wxIsKindOf(this, MyMixInClass)) DoSomething(); event.Skip(); } DECLARE_CLASS(MyFrame); DECLARE_EVENT_TABLE() }; IMPLEMENT_CLASS2(MyFrame, wxFrame, MyMixInClass) BEGIN_EVENT_TABLE(MyFrame, wxFrame) EVT_MENU(wxID_HELP, MyFrame::OnHelp) END_EVENT_TABLE() class MyApp: public wxApp { public: bool OnInit(void) { new MyFrame(NULL); return TRUE; } }; IMPLEMENT_APP(MyApp)
How to fix up wxDynamicCast so it works for mix-in classes
Now, let's say that you take the example at the end of the previous section and replace MyApp::OnInit() with some new code.
class MyApp: public wxApp { public: bool OnInit(void) { wxFrame * pFrame = new MyFrame(NULL); MyMixInClass * pMixIn = wxDynamicCast(pFrame, MyMixInClass); if (pMixIn) pMixIn->DoSomething(); return TRUE; } };
It won't really work. Yes, pMixIn will be set to NULL if MyMixInClass is not a base class of pFrame. It will also be non-NULL if MyMixInClass is a base class of pFrame. But, when pMixIn is non-NULL, the pointer will be corrupt and you'll get crashes or weird occurrences if you try to use it. The source code for wxDynamicCast reveals why this happens.
#define wxDynamicCast(obj, className) \ ((className *) wxCheckDynamicCast((wxObject*)(obj), &className::sm_class##className))
You see, wxDynamicCast() casts the pointer blindly to a (wxObject *) and then casts it blindly to the desired class (e.g. MyMixInClass). That works for single inheritance because the data and functions for the base classes never have an offset. It does not work for multiple inheritance because the it ignores the need to use arthimetic to offset the pointer to the proper part of the object to access the mix-in class object data and methods. If that doesn't make any sense, don't worry: you don't really need to care.
To fix it, wxDynamicCast needs help. It needs to know what possible derived classes that the pointer might point to so it can fix up the pointer to point at the correct offset. So, you've got to call another macro that checks RTTI and then does a bunch of casting magic to fix up the pointer (if your suggested derived class is correct) or change nothing (if your suggested derived class is wrong).
Understand? Ok, maybe not. Maybe the code will be easier.
// the black magic macro to fix up the pointer #define wxDynamicCastHelper(obj, className, mixInClassName) \ (obj && wxIsKindOf(((wxObject *)(void *)obj), className))? \ (mixInClassName *)(className *)(void *)obj: \ obj class MyApp: public wxApp { public: bool OnInit(void) { wxFrame * pFrame = new MyFrame(NULL); MyMixInClass * pMixIn = wxDynamicCast(pFrame, MyMixInClass); pMixIn = wxDynamicCastHelper(pMixIn, MyFrame, MyMixInClass); if (pMixIn) pMixIn->DoSomething(); return TRUE; } };
The wxDynamicCastHelper() macro works by taking the corrupt pointer and casting it back to a valid void pointer. (If you are trying to use wxWidgets RTTI on a non-wxObject derived class, wxDynamicCast() will not compile.) Then, it creates a wxObject pointer to see if the actual class is of your suggested derived class (i.e. className). If it is, it forces a cast to your suggested derived class and then forces a cast to your mix-in class. By doing these casts in this order, the compiler does the arthmetic to create a pointer that is offset to point at the data and virtual functions of the mix-in, rather than wrongly transforming the pointer type and simply treating the first derived class's part of the object as the mix-in class's part of the object.
You can use as many of these wxDynamicCastHelper() macros as you need since you may have one mix-in class that is mixed into a bunch of wxObject-derived classes.
So, for example, you have code like this:
class MyWindow: public wxWindow, public MyMixInClass ... class MyToolbar: public wxWindow, public MyMixInClass ... class MyPanel: public wxPanel, public MyMixInClass ...
You can do this:
class MyApp: public wxApp { public: bool OnInit(void) { wxFrame * pFrame = new MyFrame(NULL); MyMixInClass * pMixIn = wxDynamicCast(pFrame, MyMixInClass); pMixIn = wxDynamicCastHelper(pMixIn, MyWindow, MyMixInClass); pMixIn = wxDynamicCastHelper(pMixIn, MyToolbar, MyMixInClass); pMixIn = wxDynamicCastHelper(pMixIn, MyPanel, MyMixInClass); if (pMixIn) pMixIn->DoSomething(); return TRUE; } };
And, the call to DoSomething() will work.
Now, I agree: having to help out wxDynamicCast() is very annoying and you are likely to forget calls to wxDynamicCastHelper() in various places as you refactor your hierarchy.
Hey, I don't know what to tell you. I never said that it worked great. If you've got a better idea, e-mail me.
How to add event tables to mix-in classes
If you want to add an event table to a mix-in class, you need another new macro. BEGIN_EVENT_TABLE() takes two arguments, the class name and the base class name. If your mix-in class has no base class, you can't use this macro. So, I've created a new macro, BEGIN_EVENT_TABLE0(), that only takes one argument, the name of the class.
// the black magic macro to declare an event table without // a base class #define BEGIN_EVENT_TABLE0(theClass) \ const wxEventTable *theClass::GetEventTable() const \ { return &theClass::sm_eventTable; } \ const wxEventTable theClass::sm_eventTable = \ { NULL, &theClass::sm_eventTableEntries[0] }; \ const wxEventTableEntry theClass::sm_eventTableEntries[] = { \
This allows you to create a mix-in class like:
class MyMixInClass { public: MyMixInClass() { } virtual ~MyMixInClass() { } virtual void DoSomething() { wxMessageBox(wxT("In MyMixInClass::DoSomething()")); } void OnHelp(wxCommandEvent& event) { wxMessageBox(wxT("In MyMixInClass::OnHelp()")); event.Skip(); } DECLARE_CLASS(MyMixInClass); DECLARE_EVENT_TABLE() }; IMPLEMENT_CLASS0(MyMixInClass) BEGIN_EVENT_TABLE0(MyMixInClass) EVT_MENU(wxID_HELP, MyMixInClass::OnHelp) END_EVENT_TABLE()
Now you have an event table but it won't be automatically called by derived classes. You can call it manually, of course, but when an event arrives, the event will only be processed by the regular base class, not the mix-in class (too).
Internally, wxWidgets uses mix-in classes itself, specifically wxScrollHelper. To handle events in that mix-in class, wxWidgets creates a new object called wxScrollHelperEvtHandler (which is derived from wxEvtHandler) that processes events and then invokes specific functions on the mix-in class.
wxScrollHelper doesn't have an event table (which is different from what we are trying to do here), it just has methods such as HandleOnScroll(), HandleOnSize(), HandleOnChar() and so on which are invoked directly by wxScrollHelperEvtHandler. wxScrollHelperEvtHandler doesn't even have an event table itself, even though it is derived from wxEvtHandler and could have one; it overrides ProcessEvent() and invokes the HandleXXX() methods on an wxScrollHelper instance which is kept as a protected data member.
wxScrollHelper and wxScrollHelperEvtHandler might be changed in the future to do what we're going to do here now. But, we're going to do provide a generic wxEvtHandler derived class that uses ''the event table of another object'', not its own. Such as a mix-in class.
The code looks like this:
class wxEvtHandlerContainer : public wxEvtHandler { public: wxEvtHandlerContainer(void * pMixInObj, const wxEventTable * pEventTable): m_pMixInObj(pMixInObj), m_pEventTable(pEventTable) { } virtual bool SearchEventTable(wxEventTable& table, wxEvent& event) { wxEventType eventType = event.GetEventType(); int eventId = event.GetId(); // BC++ doesn't like testing for m_fn without != 0 for ( int i = 0; table.entries[i].m_fn != 0; i++ ) { // the line using reference exposes a bug in gcc: although it _seems_ // to work, it leads to weird crashes later on during program // execution #ifdef __GNUG__ wxEventTableEntry entry = table.entries[i]; #else const wxEventTableEntry& entry = table.entries[i]; #endif // match only if the event type is the same and the id is either -1 in // the event table (meaning "any") or the event id matches the id // specified in the event table either exactly or by falling into // range between first and last if ( eventType == entry.m_eventType ) { int tableId1 = entry.m_id, tableId2 = entry.m_lastId; if ((tableId1 == -1) || (tableId2 == -1 && eventId == tableId1) || (tableId2 != -1 && (eventId >= tableId1 && eventId <= tableId2)) ) { event.Skip(FALSE); event.m_callbackUserData = entry.m_callbackUserData; // next line is the only difference from wxEvtHandler::SearchEventTable() (((wxEvtHandler *)m_pMixInObj)->*((wxEventFunction) (entry.m_fn)))(event); return !event.GetSkipped(); } } } return FALSE; } virtual ~wxEvtHandlerContainer() { } virtual const wxEventTable * GetEventTable() const { return m_pEventTable; } protected: void * m_pMixInObj; const wxEventTable * m_pEventTable; DECLARE_CLASS(wxEvtHandlerContainer); }; IMPLEMENT_CLASS(wxEvtHandlerContainer, wxEvtHandler)
wxEventTable is a wxWidgets internal class which has instances secretly created for each event handling class when the DECLARE_EVENT_TABLE(), BEGIN_EVENT_TABLE() and our own BEGIN_EVENT_TABLE0() macros are invoked. wxEvtHandler has its own wxEventTable object but wxEvtHandlerContainer ignores its own instance and uses the wxEventTable pointer passed into its constructor for everything. So, when wxEvtHandlerContainer::ProcessEvent() is called, it actually invokes methods on ''some other object'', that is, the object that owns the wxEventTable instance.
This allows us to pass wxEventTable of a MyMixInClass instance to the constructor of wxEvtHandlerContainer instance. From then on, any events received by the wxEvtHandlerContainer are handled by MyMixInClass. This makes MyMixInClass to seem to behave like it is derived from wxEvtHandler, even though it is not.
SearchEventTable() in wxEvtHandlerContainer is overridden to cope with a nasty method pointer bug that I discovered after using this code for a while. When an event is handled and SearchEventTable() is invoked, the correct event method is invoked but it uses the wxEvtHandlerContainer instance as the "this" pointer, rather than the MyMixInClass instance. When this happens, the compiler accesses the wxEvtHandlerContainer instance as if it were a MyMixInClass instance. As a result, data members accessed from inside an event handler would be corrupt and have the wrong values. To correct this bug, the method pointers are invoked using m_pMixInObj as the "this" pointer. The unorthodox cast of m_pMixInObj to wxEvtHandler, ''even though it is not derived from wxEvtHandler'', subverts the C++ type system but, having tested it on both Windows and Linux, seems reliable and cross-platform.
To create an event handler for a mix-in class, you would use the following code:
wxEvtHandlerContainer * evtHandlerContainer = new wxEvtHandlerContainer((MyMixInClass *)this, MyMixInClass::GetEventTable())
However, creating an event handler for a mix-in class is not enough. The event handler must be added to the base class event handling. All wxEvtHandler-derived objects can have multiple event handlers and they are handled in order. The only requirement is that an event handler must be derived from wxEvtHandler and, of course, we created wxEvtHandlerContainer specifically to fulfill this requirement.
Every class derived from wxWindow contains a linked list of event handlers. The first event handler can be retrieved by invoking the GetEventHandler() method and, often, there is only one event handler in the list. If there are others, they can be accessed by using the GetPreviousHandler() and GetNextHandler() methods on the event handlers themselves. So, GetEventHandler() would return the first event handler for a window and GetEventHandler()->GetNextHandler() would return the second event handler.
An event handler, such as evtHandlerContainer, can be added to the base class in one of two ways.
The first way is to use code like this:
wxEvtHandler * evtHandler = GetEventHandler(); while (evtHandler->GetNextHandler()) { evtHandler = evtHandler->GetNextHandler(); } evtHandler->SetNextHandler(evtHandlerContainer);
The first way offers us the most flexibility. We can easily change the code to put the event handler anywhere in the list, including in the first position.
The second way is to use code like this:
PushEventHandler(evtHandlerContainer);
The second way is simply shorter. The PushEventHandler() method treats the linked list of event handlers like a stack, allowing us to add an event handler that will be called only after all other event handlers have been called. There is a corresponding PopEventHandler() method that will remove the last event handler from the linked list.
Since I first wrote this article, I've concluded that using the first method is better than the second method. The second method is inflexible and quite brittle since changing the order of event handlers using PushEventHandler()/PopEventHandler() requires knowing the number and use of all existing event handlers. In addition, using PopEventHandler() to remove event handlers causes code changes if the event handler is moved around.
So, using the first method, the code for our derived class looks like this:
class MyFrame: public wxFrame, public MyMixInClass { public: MyFrame() { wxEvtHandler * evtHandler = GetEventHandler(); while (evtHandler->GetNextHandler()) { evtHandler = evtHandler->GetNextHandler(); } evtHandler->SetNextHandler(new wxEvtHandlerContainer((MyMixInClass *)this, MyMixInClass::GetEventTable())); } virtual ~MyFrame() { wxEvtHandler * evtHandler = GetEventHandler(); while (evtHandler) { wxEvtHandler * evtHandlerNext = evtHandler->GetNextHandler(); if (wxIsKindOf(evtHandler, wxEvtHandlerContainer)) { if (evtHandler == GetEventHandler()) { SetEventHandler(evtHandlerNext); } delete evtHandler; } evtHandler = evtHandlerNext; } } void OnHelp(wxCommandEvent& event) { wxMessageBox(wxT("In MyFrame::OnHelp()")); event.Skip(); } DECLARE_CLASS(MyFrame); DECLARE_EVENT_TABLE() }; IMPLEMENT_CLASS2(MyFrame, wxFrame, MyMixInClass) BEGIN_EVENT_TABLE(MyFrame, wxFrame) EVT_MENU(wxID_HELP, MyFrame::OnHelp) END_EVENT_TABLE()
Notice the destructor code:
virtual ~MyFrame() { wxEvtHandler * evtHandler = GetEventHandler(); while (evtHandler) { wxEvtHandler * evtHandlerNext = evtHandler->GetNextHandler(); if (wxIsKindOf(evtHandler, wxEvtHandlerContainer)) { if (evtHandler == GetEventHandler()) { SetEventHandler(evtHandlerNext); } delete evtHandler; } evtHandler = evtHandlerNext; } }
Deleting event handlers is important for several reasons. One is to avoid memory leaks. But, more importantly, wxWidgets classes, such as wxScrollHelper, use their own extra event handlers and wxWidgets will crash if it cannot find these extra event handlers where it expects them to be. (It is somewhat brittle and could be fixed by the wxWidgets team.) To easily delete the added event handlers, the code deletes any event handlers in the list that are of type wxEvtHandlerContainer. In development, the position of the new event handlers may change so it is best to delete by type, rather than position in the list. It is for this reason that PopEventHandler() is not recommended for destruction since PopEventHandler() relies on the position of the event handler in the list.
Setting mix-in event handlers as the last event handlers to be called seems natural but, in practice, I've found it preferable to put the mix-in event handlers first. Windows will often propogate wxCommandEvents to their parents and, if this happens, putting the mix-in event handlers at the end will cause it to never receive wxCommandEvents.
To put a mix-in event handler at the beginning of the list, as I recommend, substitute this code into the MyFrame constructor:
wxEvtHandler * evtHandler = GetEventHandler(); SetEventHandler(new wxEvtHandlerContainer((MyMixInClass *)this, MyMixInClass::GetEventTable())); GetEventHandler()->SetNextHandler(evtHandler); evtHandler->SetPreviousHandler(GetEventHandler());
Even more exotic algorithms are possible. Perhaps, in the future, I'll detail them here.
In the full source code sample below, you can see that calling Skip() on an event works as it always does: calling Skip() in MyMixInClass::OnHelp() allows MyFrame::OnHelp() to receive the same event later. Failing to call Skip() ends event processing right there and MyFrame::OnHelp() won't receive it.
Listing 2: Mix-in class with event table example
class wxEvtHandlerContainer : public wxEvtHandler { public: wxEvtHandlerContainer(void * pMixInObj, const wxEventTable * pEventTable): m_pMixInObj(pMixInObj), m_pEventTable(pEventTable) { } virtual bool SearchEventTable(wxEventTable& table, wxEvent& event) { wxEventType eventType = event.GetEventType(); int eventId = event.GetId(); // BC++ doesn't like testing for m_fn without != 0 for ( int i = 0; table.entries[i].m_fn != 0; i++ ) { // the line using reference exposes a bug in gcc: although it _seems_ // to work, it leads to weird crashes later on during program // execution #ifdef __GNUG__ wxEventTableEntry entry = table.entries[i]; #else const wxEventTableEntry& entry = table.entries[i]; #endif // match only if the event type is the same and the id is either -1 in // the event table (meaning "any") or the event id matches the id // specified in the event table either exactly or by falling into // range between first and last if ( eventType == entry.m_eventType ) { int tableId1 = entry.m_id, tableId2 = entry.m_lastId; if ((tableId1 == -1) || (tableId2 == -1 && eventId == tableId1) || (tableId2 != -1 && (eventId >= tableId1 && eventId <= tableId2)) ) { event.Skip(FALSE); event.m_callbackUserData = entry.m_callbackUserData; // next line is the only difference from wxEvtHandler::SearchEventTable() (((wxEvtHandler *)m_pMixInObj)->*((wxEventFunction) (entry.m_fn)))(event); return !event.GetSkipped(); } } } return FALSE; } virtual ~wxEvtHandlerContainer() { } virtual const wxEventTable * GetEventTable() const { return m_pEventTable; } protected: void * m_pMixInObj; const wxEventTable * m_pEventTable; DECLARE_CLASS(wxEvtHandlerContainer); }; IMPLEMENT_CLASS(wxEvtHandlerContainer, wxEvtHandler) #define IMPLEMENT_CLASS0(name) \ wxClassInfo name::sm_class##name(wxT(#name), 0, 0, (int) sizeof(name), (wxObjectConstructorFn) 0); // the black magic macro to declare an event table without // a base class #define BEGIN_EVENT_TABLE0(theClass) \ const wxEventTable *theClass::GetEventTable() const \ { return &theClass::sm_eventTable; } \ const wxEventTable theClass::sm_eventTable = \ { NULL, &theClass::sm_eventTableEntries[0] }; \ const wxEventTableEntry theClass::sm_eventTableEntries[] = { \ // a mix-in class class MyMixInClass { public: MyMixInClass() { } virtual ~MyMixInClass() { } void OnHelp(wxCommandEvent& event) { wxMessageBox(wxT("In MyMixInClass::OnHelp()")); event.Skip(); } DECLARE_CLASS(MyMixInClass); DECLARE_EVENT_TABLE() }; IMPLEMENT_CLASS0(MyMixInClass) BEGIN_EVENT_TABLE0(MyMixInClass) EVT_MENU(wxID_HELP, MyMixInClass::OnHelp) END_EVENT_TABLE() // a base class class MyFrameBase: public wxFrame { public: MyFrameBase(wxWindow * parent): wxFrame(parent, wxID_ANY, wxT("Demo"), wxDefaultPosition, wxSize(200, 200)) { wxMenu * pMenu = new wxMenu(); pMenu->Append(wxID_HELP, wxT("Help")); wxMenuBar * pMenuBar = new wxMenuBar; pMenuBar->Append(pMenu, wxT("&File")); SetMenuBar(pMenuBar); Show(); } virtual ~MyFrameBase() { } void OnHelp(wxCommandEvent& event) { wxMessageBox(wxT("In MyFrameBase::OnHelp()")); event.Skip(); } DECLARE_CLASS(MyFrameBase); DECLARE_EVENT_TABLE(); }; IMPLEMENT_CLASS(MyFrameBase, wxFrame) BEGIN_EVENT_TABLE(MyFrameBase, wxFrame) EVT_MENU(wxID_HELP, MyFrameBase::OnHelp) END_EVENT_TABLE() // a derived class with multiple inheritance class MyFrame: public MyFrameBase, public MyMixInClass { public: MyFrame(wxWindow * parent): MyFrameBase(parent) { wxEvtHandler * evtHandler = GetEventHandler(); SetEventHandler(new wxEvtHandlerContainer((MyMixInClass *)this, MyMixInClass::GetEventTable())); GetEventHandler()->SetNextHandler(evtHandler); evtHandler->SetPreviousHandler(GetEventHandler()); } virtual ~MyFrame() { wxEvtHandler * evtHandler = GetEventHandler(); while (evtHandler) { wxEvtHandler * evtHandlerNext = evtHandler->GetNextHandler(); if (wxIsKindOf(evtHandler, wxEvtHandlerContainer)) { if (evtHandler == GetEventHandler()) { SetEventHandler(evtHandlerNext); } delete evtHandler; } evtHandler = evtHandlerNext; } } void OnHelp(wxCommandEvent& event) { wxMessageBox(wxT("In MyFrame::OnHelp()")); event.Skip(); } DECLARE_CLASS(MyFrame); DECLARE_EVENT_TABLE(); }; IMPLEMENT_CLASS2(MyFrame, MyFrameBase, MyMixInClass) BEGIN_EVENT_TABLE(MyFrame, MyFrameBase) EVT_MENU(wxID_HELP, MyFrame::OnHelp) END_EVENT_TABLE() class MyApp: public wxApp { public: bool OnInit(void) { new MyFrame(NULL); return TRUE; } }; IMPLEMENT_APP(MyApp)
How to call Connect() with mix-in classes
Changing a class from single inheritance to multiple inheritance, even using a very simple mix-in class, does require more casting in some cases. Consider the following single inheritance code:
class MyFrame: public wxFrame { public: MyFrame(wxWindow * parent): wxFrame(parent, wxID_ANY, wxT("Demo"), wxDefaultPosition, wxSize(200, 200)) { wxMenu * pMenu = new wxMenu(); pMenu->Append(wxID_HELP, wxT("Help")); wxMenuBar * pMenuBar = new wxMenuBar; pMenuBar->Append(pMenu, wxT("&File")); SetMenuBar(pMenuBar); Show(); Connect(wxID_HELP, wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction)&MyFrame::OnHelp); } virtual ~MyFrame() { } void OnHelp(wxCommandEvent& event) { wxMessageBox(wxT("In MyFrame::OnHelp()")); } };
Now, suppose that a very simple mix-in class is added. The multiple inheritance source code becomes:
class MyMixInClass { public: MyMixInClass() { } virtual ~MyMixInClass() { } }; class MyFrame: public wxFrame, public MyMixInClass { public: MyFrame(wxWindow * parent): wxFrame(parent, wxID_ANY, wxT("Demo"), wxDefaultPosition, wxSize(200, 200)) { wxMenu * pMenu = new wxMenu(); pMenu->Append(wxID_HELP, wxT("Help")); wxMenuBar * pMenuBar = new wxMenuBar; pMenuBar->Append(pMenu, wxT("&File")); SetMenuBar(pMenuBar); Show(); Connect(wxID_HELP, wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction)(void (wxFrame::*)(wxCommandEvent&))&MyFrame::OnHelp); } virtual ~MyFrame() { } void OnHelp(wxCommandEvent& event) { wxMessageBox(wxT("In MyFrame::OnHelp()")); } };
Notice, the single inheritance Connect() call isn't too difficult:
Connect(wxID_HELP, wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction)&MyFrame::OnHelp);
But the multiple inheritance Connect() call becomes the more complicated:
Connect(wxID_HELP, wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction)<font color="red">(void (wxFrame::*)(wxCommandEvent&))</font>&MyFrame::OnHelp);
The additional cast is highlighted above in red. It tells the compiler to use the wxFrame base class, rather than the MyMixInClass base class, before applying the generic wxObjectEventFunction cast.
How to use virtual base classes in mix-in classes
The benefits and difficulties of using virtual base classes in wxWidgets are not well understood. Yes, there is a method for creating mix-in classes that derive from wxEvtHandler or wxObject. But there seem to be few benefits for the trouble involved. Still, the technique is presented here so you can decide for yourself.
The technique requires a new set of event handling macros that mirror the existing macros (e.g. EVT_MENU, EVT_BUTTON).
When you try to use the existing wxWidgets event handling macros, the compiler gives error messages like:
error C2594: 'type cast' : ambiguous conversions from 'void (__thiscall MyFrame::*)(class wxCommandEvent &)' to 'void (__thiscall wxEvtHandler::*)(class wxCommandEvent &)'
The new set of macros resolve the ambiguity and eliminate the error by making an extra cast inside the macro to a base class of the class that inherits from multiple classes. By casting to a base class, the compiler does not have to distinguish which base class to use in doing a subsequent cast to the common base class (i.e. wxEvtHandler).
Consider the following code snippet:
class MyMixInClass : public wxEvtHandler { public: MyMixInClass() { } virtual ~MyMixInClass() { } virtual bool DoSomething() { return true; } void OnHelp(wxCommandEvent& event) { wxMessageBox("In MyMixInClass::OnHelp()"); event.Skip(); } DECLARE_CLASS(MyMixInClass); DECLARE_EVENT_TABLE(); }; IMPLEMENT_CLASS(MyMixInClass, wxEvtHandler); BEGIN_EVENT_TABLE(MyMixInClass, wxEvtHandler) EVT_BUTTON(wxID_HELP, MyMixInClass::OnHelp) END_EVENT_TABLE() class MyFrame: public wxFrame, virtual public MyMixInClass { public: MyFrame(wxWindow * parent): wxFrame(parent, wxID_ANY, "Demo", wxDefaultPosition, wxSize(200, 200)) { new wxButton(this, wxID_HELP, "Help"); Show(); } virtual ~MyFrame() { } void OnHelp(wxCommandEvent& event) { wxMessageBox("In MyFrame::OnHelp()"); event.Skip(); } DECLARE_CLASS(MyFrame); DECLARE_EVENT_TABLE() }; IMPLEMENT_CLASS2(MyFrame, wxFrame, MyMixInClass) BEGIN_EVENT_TABLE(MyFrame, wxFrame) EVT_BUTTON(wxID_HELP, MyFrame::OnHelp) END_EVENT_TABLE()
This does not compile; the Visual C++ compiler shows errors specifically for the event table entries.
To fix it, we need to help the compiler. Unfortunately, there isn't an easy macro that I could provide to coerce the current macros to work because all the event handling macros (EVT_MENU(), EVT_SET_FOCUS(), etc.) take functions, not function pointers.
<html>Side Note: Seemingly, you can convert a function into a function pointer but you can't do the reverse. Since I need to do casts, I need convert the function to a function pointer to make the casts work but then I dereference that pointer and pass the function into the regular macros so that those macros can dereference the pointer again. If anybody figures out how to do this, send e-mail to my address which is at the bottom of this page.</html>
So, instead of providing an easy macro, you'll have to decode the existing macros and then create your own macros that do the appropriate cast. Ugh.
I've done this for the EVT_MENU() and EVT_BUTTON() macros. Below, I've created two new macros, EVTMI_MENU() and EVTMI_BUTTON(), that operates similar to the EVT_MENU() and EVT_BUTTON() but works for multiple inheritance.
#define EVTMI_MENU(baseClass, id, fn) DECLARE_EVENT_TABLE_ENTRY( wxEVT_COMMAND_MENU_SELECTED, id, wxID_ANY, (wxObjectEventFunction) (wxEventFunction) (wxCommandEventFunction) (void (baseClass::*)(wxCommandEvent&)) & fn, (wxObject *) NULL ), #define EVTMI_BUTTON(baseClass, id, fn) DECLARE_EVENT_TABLE_ENTRY( wxEVT_COMMAND_BUTTON_CLICKED, id, wxID_ANY, (wxObjectEventFunction) (wxEventFunction) (wxCommandEventFunction) (void (baseClass::*)(wxCommandEvent&)) & fn, (wxObject *) NULL ), BEGIN_EVENT_TABLE(MyFrame, wxFrame) EVTMI_MENU(wxFrame, wxID_HELP, MyFrame::OnHelp) END_EVENT_TABLE()
Each macro takes an additional argument: the second argument is a base class name to use to resolve the ambiguity. The code will now compile and execute without errors.
Creating your own multiple inheritance event macros is not hard but it does take some work.
When you get the compiler message with the standard event macros, that message tells you that the compiler cannot decide which of your two base classes to use when making a cast from your derived object to wxEvtHandler. You can't see the cast because it is hidden inside the standard event macro. To fix it, you have to look inside the macro definition.
The macro definitions are inside the file, include\wx\event.h, in your wxWindows directory. To find a particular definition, such as the EVT_LEFT_UP() macro definition, search for "#define EVT_LEFT_UP" (or whatever macro that you are looking for). You can also use the Go to Definition menu item when you right click on EVT_LEFT_UP if you are using Visual C++.) The original EVT_LEFT_UP looks like this:
#define EVT_LEFT_UP(func) DECLARE_EVENT_TABLE_ENTRY( wxEVT_LEFT_UP, wxID_ANY, wxID_ANY, (wxObjectEventFunction) (wxEventFunction) (wxMouseEventFunction) & func, (wxObject *) NULL ),
And the new EVTMI_LEFT_UP() macro will look like this:
#define EVT<font color="red">MI</font>_LEFT_UP(<font color="red">baseClass, </font>func) DECLARE_EVENT_TABLE_ENTRY( wxEVT_LEFT_UP, wxID_ANY, wxID_ANY, (wxObjectEventFunction) (wxEventFunction) (wxMouseEventFunction) <font color="red">(void (baseClass::*)(wxMouseEvent&))</font> & func, (wxObject *) NULL ),
The differences are shown in red. As you can see, the only real difference between EVTMI_LEFT_UP and EVT_LEFT_UP is an extra cast, (void (baseClass::*)(wxMouseEvent&)), which will cast from your derived class to one of your base classes.
The wxMouseEvent is of the same type as the method parameter, for example, void MyFrame::OnLeftUp(wxMouseEvent& event). You'll need to change this depending on the parameter to the event handler for the event itself.
So, to create your own event handling macro, you only need to (A) copy the original macro, such as EVT_LEFT_UP, (B) change the name, such as to EVTMI_LEFT_UP (C) add the baseClass parameter so it looks like EVTMI_LEFT_UP(baseClass, func) rather than EVT_LEFT_UP(func) and (D) add the extra cast, which is like (void (baseClass::*)(wxMouseEvent&)).
The full source code for an example that uses these techniques is:
Listing 3: Virtual base class example
class wxEvtHandlerContainer : public wxEvtHandler { public: wxEvtHandlerContainer(void * pMixInObj, const wxEventTable * pEventTable): m_pMixInObj(pMixInObj), m_pEventTable(pEventTable) { } virtual bool SearchEventTable(wxEventTable& table, wxEvent& event) { wxEventType eventType = event.GetEventType(); int eventId = event.GetId(); // BC++ doesn't like testing for m_fn without != 0 for ( int i = 0; table.entries[i].m_fn != 0; i++ ) { // the line using reference exposes a bug in gcc: although it _seems_ // to work, it leads to weird crashes later on during program // execution #ifdef __GNUG__ wxEventTableEntry entry = table.entries[i]; #else const wxEventTableEntry& entry = table.entries[i]; #endif // match only if the event type is the same and the id is either -1 in // the event table (meaning "any") or the event id matches the id // specified in the event table either exactly or by falling into // range between first and last if ( eventType == entry.m_eventType ) { int tableId1 = entry.m_id, tableId2 = entry.m_lastId; if ((tableId1 == -1) || (tableId2 == -1 && eventId == tableId1) || (tableId2 != -1 && (eventId >= tableId1 && eventId <= tableId2)) ) { event.Skip(FALSE); event.m_callbackUserData = entry.m_callbackUserData; // next line is the only difference from wxEvtHandler::SearchEventTable() (((wxEvtHandler *)m_pMixInObj)->*((wxEventFunction) (entry.m_fn)))(event); return !event.GetSkipped(); } } } return FALSE; } virtual ~wxEvtHandlerContainer() { } virtual const wxEventTable * GetEventTable() const { return m_pEventTable; } protected: void * m_pMixInObj; const wxEventTable * m_pEventTable; DECLARE_CLASS(wxEvtHandlerContainer); }; IMPLEMENT_CLASS(wxEvtHandlerContainer, wxEvtHandler) #define IMPLEMENT_CLASS0(name) \ wxClassInfo name::sm_class##name(wxT(#name), 0, 0, (int) sizeof(name), (wxObjectConstructorFn) 0); #define EVTMI_MENU(baseClass, id, fn) DECLARE_EVENT_TABLE_ENTRY( wxEVT_COMMAND_MENU_SELECTED, id, wxID_ANY, (wxObjectEventFunction) (wxEventFunction) (wxCommandEventFunction) (void (baseClass::*)(wxCommandEvent&)) & fn, (wxObject *) NULL ), // a mix-in class class MyMixInClass: public wxEvtHandler { public: MyMixInClass() { } virtual ~MyMixInClass() { } void OnHelp(wxCommandEvent& event) { wxMessageBox(wxT("In MyMixInClass::OnHelp()")); event.Skip(); } DECLARE_CLASS(MyMixInClass); DECLARE_EVENT_TABLE() }; IMPLEMENT_CLASS(MyMixInClass, wxEvtHandler) BEGIN_EVENT_TABLE(MyMixInClass, wxEvtHandler) EVT_MENU(wxID_HELP, MyMixInClass::OnHelp) END_EVENT_TABLE() // a base class class MyFrameBase: public wxFrame { public: MyFrameBase(wxWindow * parent): wxFrame(parent, wxID_ANY, wxT("Demo"), wxDefaultPosition, wxSize(200, 200)) { wxMenu * pMenu = new wxMenu(); pMenu->Append(wxID_HELP, wxT("Help")); wxMenuBar * pMenuBar = new wxMenuBar; pMenuBar->Append(pMenu, wxT("&File")); SetMenuBar(pMenuBar); Show(); } virtual ~MyFrameBase() { } void OnHelp(wxCommandEvent& event) { wxMessageBox(wxT("In MyFrameBase::OnHelp()")); event.Skip(); } DECLARE_CLASS(MyFrameBase); DECLARE_EVENT_TABLE(); }; IMPLEMENT_CLASS(MyFrameBase, wxFrame) BEGIN_EVENT_TABLE(MyFrameBase, wxFrame) EVT_MENU(wxID_HELP, MyFrameBase::OnHelp) END_EVENT_TABLE() // a derived class with multiple inheritance class MyFrame: public MyFrameBase, virtual public MyMixInClass { public: MyFrame(wxWindow * parent): MyFrameBase(parent) { wxEvtHandler * evtHandler = GetEventHandler(); SetEventHandler(new wxEvtHandlerContainer((MyMixInClass *)this, MyMixInClass::GetEventTable())); GetEventHandler()->SetNextHandler(evtHandler); evtHandler->SetPreviousHandler(GetEventHandler()); } virtual ~MyFrame() { wxEvtHandler * evtHandler = GetEventHandler(); while (evtHandler) { wxEvtHandler * evtHandlerNext = evtHandler->GetNextHandler(); if (wxIsKindOf(evtHandler, wxEvtHandlerContainer)) { if (evtHandler == GetEventHandler()) { SetEventHandler(evtHandlerNext); } delete evtHandler; } evtHandler = evtHandlerNext; } } void OnHelp(wxCommandEvent& event) { wxMessageBox(wxT("In MyFrame::OnHelp()")); event.Skip(); } DECLARE_CLASS(MyFrame); DECLARE_EVENT_TABLE(); }; IMPLEMENT_CLASS2(MyFrame, MyFrameBase, MyMixInClass) BEGIN_EVENT_TABLE(MyFrame, MyFrameBase) EVTMI_MENU(MyFrameBase, wxID_HELP, MyFrame::OnHelp) END_EVENT_TABLE() class MyApp: public wxApp { public: bool OnInit(void) { new MyFrame(NULL); return TRUE; } }; IMPLEMENT_APP(MyApp)
How wxWidgets itself could make virtual base classes easier
Changes to wxWidgets source code itself could actually simplify the development of EVTMI_MENU() style macros as well as provide more general flexibility.
The current implementation is:
#define EVT_MENU(id, fn) DECLARE_EVENT_TABLE_ENTRY( wxEVT_COMMAND_MENU_SELECTED, id, wxID_ANY, (wxObjectEventFunction) (wxEventFunction) (wxCommandEventFunction) & fn, (wxObject *) NULL ),
The code below shows how wxWidgets could restructure the EVT_MENU macro to be more useful:
#define EVTP_MENU(id, fnp) DECLARE_EVENT_TABLE_ENTRY( wxEVT_COMMAND_MENU_SELECTED, id, wxID_ANY, (wxObjectEventFunction) (wxEventFunction) (wxCommandEventFunction) fnp, (wxObject *) NULL ), #define EVT_MENU(id, fn) EVTP_MENU(id, & fn)
This would allow an event table like:
BEGIN_EVENT_TABLE(MyFrame, MyFrameBase) EVTP_MENU(wxID_HELP, (void (MyFrameBase::*)(wxCommandEvent&)) & MyFrame::OnHelp) END_EVENT_TABLE()
Or like this:
#define EVTMI_MENU(baseClass, id, fn) EVTP_MENU(id, (void (baseClass::*)(wxCommandEvent&)) & fn) BEGIN_EVENT_TABLE(MyFrame, MyFrameBase) EVTMI_MENU(MyFrameBase, wxID_HELP, MyFrame::OnHelp) END_EVENT_TABLE()
Naturally, this change would need to be applied to all event handling macros.
How to use non-virtual base classes in mix-in classes
Similar to using virtual base classes in wxWidgets, the benefits and difficulties of using non-virtual base classes in wxWidgets are not well understood. Still, the technique is presented here so you can decide for yourself.
Non-virtual base classes use the same code as virtual base classes, except that the virtual keyword is dropped. Non-virtual base classes appear to work fine when one of the classes is directly derived from wxEvtHandler. However, if both classes are indirectly derived from wxEvtHandler (say, both were derived from wxWindow), it is likely that some methods may not work correctly when called from inside the second base class.
If I experiment more, I will add more information on this subject.
The full source code for an example that uses this technique is:
Listing 4: Non-virtual base class example
class wxEvtHandlerContainer : public wxEvtHandler { public: wxEvtHandlerContainer(void * pMixInObj, const wxEventTable * pEventTable): m_pMixInObj(pMixInObj), m_pEventTable(pEventTable) { } virtual bool SearchEventTable(wxEventTable& table, wxEvent& event) { wxEventType eventType = event.GetEventType(); int eventId = event.GetId(); // BC++ doesn't like testing for m_fn without != 0 for ( int i = 0; table.entries[i].m_fn != 0; i++ ) { // the line using reference exposes a bug in gcc: although it _seems_ // to work, it leads to weird crashes later on during program // execution #ifdef __GNUG__ wxEventTableEntry entry = table.entries[i]; #else const wxEventTableEntry& entry = table.entries[i]; #endif // match only if the event type is the same and the id is either -1 in // the event table (meaning "any") or the event id matches the id // specified in the event table either exactly or by falling into // range between first and last if ( eventType == entry.m_eventType ) { int tableId1 = entry.m_id, tableId2 = entry.m_lastId; if ((tableId1 == -1) || (tableId2 == -1 && eventId == tableId1) || (tableId2 != -1 && (eventId >= tableId1 && eventId <= tableId2)) ) { event.Skip(FALSE); event.m_callbackUserData = entry.m_callbackUserData; // next line is the only difference from wxEvtHandler::SearchEventTable() (((wxEvtHandler *)m_pMixInObj)->*((wxEventFunction) (entry.m_fn)))(event); return !event.GetSkipped(); } } } return FALSE; } virtual ~wxEvtHandlerContainer() { } virtual const wxEventTable * GetEventTable() const { return m_pEventTable; } protected: void * m_pMixInObj; const wxEventTable * m_pEventTable; DECLARE_CLASS(wxEvtHandlerContainer); }; IMPLEMENT_CLASS(wxEvtHandlerContainer, wxEvtHandler) #define IMPLEMENT_CLASS0(name) \ wxClassInfo name::sm_class##name(wxT(#name), 0, 0, (int) sizeof(name), (wxObjectConstructorFn) 0); #define EVTMI_MENU(baseClass, id, fn) DECLARE_EVENT_TABLE_ENTRY( wxEVT_COMMAND_MENU_SELECTED, id, wxID_ANY, (wxObjectEventFunction) (wxEventFunction) (wxCommandEventFunction) (void (baseClass::*)(wxCommandEvent&)) & fn, (wxObject *) NULL ), // a mix-in class class MyMixInClass: public wxEvtHandler { public: MyMixInClass() { } virtual ~MyMixInClass() { } void OnHelp(wxCommandEvent& event) { wxMessageBox(wxT("In MyMixInClass::OnHelp()")); event.Skip(); } DECLARE_CLASS(MyMixInClass); DECLARE_EVENT_TABLE() }; IMPLEMENT_CLASS(MyMixInClass, wxEvtHandler) BEGIN_EVENT_TABLE(MyMixInClass, wxEvtHandler) EVT_MENU(wxID_HELP, MyMixInClass::OnHelp) END_EVENT_TABLE() // a base class class MyFrameBase: public wxFrame { public: MyFrameBase(wxWindow * parent): wxFrame(parent, wxID_ANY, wxT("Demo"), wxDefaultPosition, wxSize(200, 200)) { wxMenu * pMenu = new wxMenu(); pMenu->Append(wxID_HELP, wxT("Help")); wxMenuBar * pMenuBar = new wxMenuBar; pMenuBar->Append(pMenu, wxT("&File")); SetMenuBar(pMenuBar); Show(); } virtual ~MyFrameBase() { } void OnHelp(wxCommandEvent& event) { wxMessageBox(wxT("In MyFrameBase::OnHelp()")); event.Skip(); } DECLARE_CLASS(MyFrameBase); DECLARE_EVENT_TABLE(); }; IMPLEMENT_CLASS(MyFrameBase, wxFrame) BEGIN_EVENT_TABLE(MyFrameBase, wxFrame) EVT_MENU(wxID_HELP, MyFrameBase::OnHelp) END_EVENT_TABLE() // a derived class with multiple inheritance class MyFrame: public MyFrameBase, public MyMixInClass { public: MyFrame(wxWindow * parent): MyFrameBase(parent) { wxEvtHandler * evtHandler = GetEventHandler(); SetEventHandler(new wxEvtHandlerContainer((MyMixInClass *)this, MyMixInClass::GetEventTable())); GetEventHandler()->SetNextHandler(evtHandler); evtHandler->SetPreviousHandler(GetEventHandler()); } virtual ~MyFrame() { wxEvtHandler * evtHandler = GetEventHandler(); while (evtHandler) { wxEvtHandler * evtHandlerNext = evtHandler->GetNextHandler(); if (wxIsKindOf(evtHandler, wxEvtHandlerContainer)) { if (evtHandler == GetEventHandler()) { SetEventHandler(evtHandlerNext); } delete evtHandler; } evtHandler = evtHandlerNext; } } void OnHelp(wxCommandEvent& event) { wxMessageBox(wxT("In MyFrame::OnHelp()")); event.Skip(); } DECLARE_CLASS(MyFrame); DECLARE_EVENT_TABLE(); }; IMPLEMENT_CLASS2(MyFrame, MyFrameBase, MyMixInClass) BEGIN_EVENT_TABLE(MyFrame, MyFrameBase) EVTMI_MENU(MyFrameBase, wxID_HELP, MyFrame::OnHelp) END_EVENT_TABLE() class MyApp: public wxApp { public: bool OnInit(void) { new MyFrame(NULL); return TRUE; } }; IMPLEMENT_APP(MyApp)
About the author
Feel free to e-mail me at [email protected] with any questions or comments.
Resources
- RTTI - Basic info on wxWidget's RTTI
- DYNAMIC CLASS Macros
- wxObject
- wxClassInfo