描述wxWidgets中事件处理的类型转化
wxWidgets是一个比较常用的UI界面库,我曾经试着使用wxWidgets写一个UI编辑工具,在此期间,学习了一些wxWidgets的知识。我对wxWidgets的绑定(Bind)比较好奇,想知道,wxWidgets是如何知道,我Bind的函数,是需要什么参数,所以查看了一些源代码,这里,将了解的知识写出来:
首先,给出绑定的相关源代码:
class wxEvtHandler : public wxObject, public wxTrackable { // These functions are used for old, untyped, event handlers and don't // check that the type of the function passed to them actually matches the // type of the event. They also only allow connecting events to methods of // wxEvtHandler-derived classes. // // The template Bind() methods below are safer and allow connecting // events to arbitrary functions or functors -- but require compiler // support for templates. // // Dynamic association of a member function handler with the event handler, // winid and event type void Connect(int winid, int lastId, wxEventType eventType, wxObjectEventFunction func, wxObject *userData = NULL, wxEvtHandler *eventSink = NULL) { DoBind(winid, lastId, eventType, wxNewEventFunctor(eventType, func, eventSink), userData); } bool Disconnect(int winid, int lastId, wxEventType eventType, wxObjectEventFunction func = NULL, wxObject* userData = NULL, wxEvtHandler* eventSink = NULL) { return DoUnbind(winid, lastId, eventType, wxMakeEventFunctor(eventType, func, eventSink), userData); } // Bind a method of a class (called on the specified handler which must // be convertible to this class) object to an event: template <typename EventTag, typename Class, typename EventArg, typename EventHandler> void Bind(const EventTag &eventType, void (Class::*method)(EventArg &), EventHandler *handler, int winid = wxID_ANY, int lastId = wxID_ANY, wxObject* userData = NULL) { DoBind(winid, lastId, eventType, wxNewEventFunctor(eventType, method, handler), userData); } template <typename EventTag, typename Class, typename EventArg, typename EventHandler> bool Unbind(const EventTag &eventType, void (Class::*method)(EventArg&), EventHandler *handler, int winid = wxID_ANY, int lastId = wxID_ANY, wxObject* userData = NULL) { return DoUnbind(winid, lastId, eventType, wxMakeEventFunctor(eventType, method, handler), userData); } void wxEvtHandler::DoBind(int id, int lastId, wxEventType eventType, wxEventFunctor *func, wxObject *userData) { wxDynamicEventTableEntry *entry = new wxDynamicEventTableEntry(eventType, id, lastId, func, userData); // Check // ... if (!m_dynamicEvents) m_dynamicEvents = new DynamicEvents; // We prefer to push back the entry here and then iterate over the vector // in reverse direction in GetNextDynamicEntry() as it's more efficient // than inserting the element at the front. m_dynamicEvents->push_back(entry); // 该函数的以下部分可忽略 // Make sure we get to know when a sink is destroyed wxEvtHandler *eventSink = func->GetEvtHandler(); if ( eventSink && eventSink != this ) { wxEventConnectionRef *evtConnRef = FindRefInTrackerList(eventSink); if ( evtConnRef ) evtConnRef->IncRef( ); else new wxEventConnectionRef(this, eventSink); } } };
我只贴出一部分,与我描述相关的内容,当然,很多时候会有些多余,不过,对于不太懂的地方,可以略过,对于比较重要的地方,我会比较详细的说明。wxWidgets使用的是通过模板来记录类型,然后,再将类型还原回来。
这里,我随便从wxWidgets中贴出一个Bind使用:
MyFrame::MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size) : wxFrame((wxFrame *)NULL, -1, title, pos, size) { wxPanel* mainPane = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS); mainPane->Bind(wxEVT_CHAR_HOOK, &MyFrame::OnKeyDown, this); }
void MyFrame::OnKeyDown(wxKeyEvent& event) { wxMessageBox(wxString::Format("KeyDown: %i\n", (int)event.GetKeyCode())); event.Skip(); }
根据wxWidgets源代码,可以知道wxEVT_CHAR_HOOK是const wxEventTypeTag<wxKeyEvent>,从Bind函数,可以知道,我们可以从两处知道OnKeyDown函数的参数是wxKeyEvent,一个是传入的参数wxEVT_CHAR_HOOK,一个是OnKeyDown。根据传参可以知道,实现只会和DoBind的wxEventType eventType,以及wxEventFunctor *func有关。wxDynamicEventTableEntry的构造函数,使用了eventType,以及func作为参数,那么类型转化会与wxDynamicEventTableEntry的实现有关吗?下面参考wxDynamicEventTableEntry的实现:
struct wxDynamicEventTableEntry : public wxEventTableEntryBase { wxDynamicEventTableEntry(int evType, int winid, int idLast, wxEventFunctor* fn, wxObject *data) : wxEventTableEntryBase(winid, idLast, fn, data), m_eventType(evType) { } // not a reference here as we can't keep a reference to a temporary int // created to wrap the constant value typically passed to Connect() - nor // do we need it int m_eventType; private: wxDynamicEventTableEntry& operator=(const wxDynamicEventTableEntry&) = delete; };
注意,这里evtType的类型是int,再向上看,其实DoBind第三个参数wxEventType实际类型就是int,也就是说,携带参数类型是wxKeyEvent的参数只有传入DoBind的wxEventFunctor *func。参看wxDynamicEventTableEntry的实现,这个结构甚至不是模板,所以不会携带类型信息。那么唯一的希望,就只有
wxNewEventFunctor(eventType, func, eventSink)构造的func指针所指示的对象,在DoBind中的wxEvtHandler *eventSink = func->GetEvtHandler();,eventSink并不携带类型信息。
现在我们要查看wxNewEventFunctor函数的实现:
template <typename EventTag, typename Class, typename EventArg, typename EventHandler> inline wxEventFunctorMethod<EventTag, Class, EventArg, EventHandler> * wxNewEventFunctor(const EventTag&, void (Class::*method)(EventArg&), EventHandler *handler) { return new wxEventFunctorMethod<EventTag, Class, EventArg, EventHandler> (method, handler); }
以及wxEventFunctorMethod的实现:
// functor forwarding the event to a method of the given object // template <typename EventTag, typename Class, typename EventArg, typename EventHandler> class wxEventFunctorMethod : public wxEventFunctor, private wxPrivate::HandlerImpl < Class, EventArg, wxIsPublicyDerived<Class, wxEvtHandler>::value != 0 > { public: void operator()(wxEvtHandler* handler, wxEvent& event) override { Class *realHandler = m_handler; if (!realHandler) { realHandler = this->ConvertFromEvtHandler(handler); // this is not supposed to happen but check for it nevertheless wxCHECK_RET(realHandler, "invalid event handler"); } // the real (run-time) type of event is EventClass and we check in // the ctor that EventClass can be converted to EventArg, so this cast // is always valid (realHandler->*method)(static_cast<EventArg&>(event)); } };
请认真看一下上面的static_cast<EventArg&>,正是这里,wxWiidgets将event类型转变为了正确的函数需要的类型。
下面贴出,wxWidgets调用上述函数的地方:
// 下面简略摘抄一下,调用处理事件(wxEvent)的函数的位置: bool wxEvtHandler::SearchDynamicEventTable(wxEvent& event) { DynamicEvents& dynamicEvents = *m_dynamicEvents; bool needToPruneDeleted = false; for (size_t n = dynamicEvents.size(); n; n--) { wxDynamicEventTableEntry* const entry = dynamicEvents[n-1]; if (!entry) { needToPruneDeleted = true; continue; } if (event.GetEventType() == entry->m_eventType) { wxEvtHandler *handler = entry->m_fn->GetEvtHandler(); if (!handler) handler = this; if (ProcessEventIfMatched(*entry, handler, event)) { return true; } } } // 以下省略 };
bool wxEvtHandler::ProcessEventIfMatched(const wxEventTableEntryBase& entry, wxEvtHandler *handler, wxEvent& event) { int tableId1 = entry.m_id, tableId2 = entry.m_lastId; // 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 // the range between first and last if ((tableId1 == wxID_ANY) || (tableId2 == wxID_ANY && tableId1 == event.GetId()) || (tableId2 != wxID_ANY && (event.GetId() >= tableId1 && event.GetId() <= tableId2))) { event.Skip(false); event.m_callbackUserData = entry.m_callbackUserData; #if wxUSE_EXCEPTIONS if (wxTheApp) { // call the handler via wxApp method which allows the user to catch // any exceptions which may be throw by any handler in the program // in one place wxTheApp->CallEventHandler(handler, *entry.m_fn, event); } else #endif // wxUSE_EXCEPTIONS { (*entry.m_fn)(handler, event); } if (!event.GetSkipped()) return true; } return false; }
// 下面再查看一下wxAppConsoleBase的CallEventHandler函数 void wxAppConsoleBase::CallEventHandler(wxEvtHandler *handler, wxEventFunctor& functor, wxEvent& event) const { // If the functor holds a method then, for backward compatibility, call // HandleEvent() wxEventFunction eventFunction = functor.GetEvtMethod(); if (eventFunction) HandleEvent(handler, eventFunction, event); else functor(handler, event); }
void wxAppConsoleBase::HandleEvent(wxEvtHandler* handler, wxEventFunction func, wxEvent& event) const { // by default, simply call the handler (handler->*func)(event); }
到这里,应该说完了wxWidgets中处理事件类型中最主要的部分,这个也是C++模板用来保证类型安全的一个具体应用。C++模板是一种非常强大的工具,合理使用,可以使代码得到很大的优化。本来,想要通过自己写的一个简单例子来进一步说明的,不过考虑这个例子已经比较长了,所以,写在下一篇随笔上面了。