消息映射表

消息是GUI程序的核心,所有的操作行为均通过消息传递。

静态消息映射表

使用静态EventTable将事件号和处理代码绑定起来,用法示例:

// 声明
class debugWXFrame: public wxFrame
{
DECLARE_EVENT_TABLE()
}; // 实现
BEGIN_EVENT_TABLE(debugWXFrame,wxFrame)
EVT_MENU(ID_MenuUser, debugWXFrame::OnCheckMenu)
END_EVENT_TABLE()

先看下定义, wxDECLARE_EVENT_TABLE用于在当前类中声明一些数据,大部分都是静态数据,另外提供了GetEventTable来访问这个表;

#define DECLARE_EVENT_TABLE()                          wxDECLARE_EVENT_TABLE();
#define wxDECLARE_EVENT_TABLE() \
private: \
static const wxEventTableEntry sm_eventTableEntries[]; \
protected: \
static const wxEventTable sm_eventTable; \
virtual const wxEventTable* GetEventTable() const; \
static wxEventHashTable sm_eventHashTable; \
virtual wxEventHashTable& GetEventHashTable() const

下面是实现,用于初始化这些静态变量,所有的消息定义都是位于wxBEGIN_EVENT_TABLEEND_EVENT_TABLE之间:

  1. sm_eventTableEntries保存消息映射表,也就是消息ID和消息处理函数关系;
  2. 实现GetEventTable方法供调用者使用;
  3. sm_eventHashTable用于保存HashTable,查找速率会提升;
#define BEGIN_EVENT_TABLE(a,b)                         wxBEGIN_EVENT_TABLE(a,b)
#define END_EVENT_TABLE() wxEND_EVENT_TABLE() #define wxBEGIN_EVENT_TABLE(theClass, baseClass) \
const wxEventTable theClass::sm_eventTable = \
{ &baseClass::sm_eventTable, &theClass::sm_eventTableEntries[0] }; \
const wxEventTable *theClass::GetEventTable() const \
{ return &theClass::sm_eventTable; } \
wxEventHashTable theClass::sm_eventHashTable(theClass::sm_eventTable); \
wxEventHashTable &theClass::GetEventHashTable() const \
{ return theClass::sm_eventHashTable; } \
const wxEventTableEntry theClass::sm_eventTableEntries[] = { \ #define wxEND_EVENT_TABLE() \
wxDECLARE_EVENT_TABLE_TERMINATOR() };

位于wxBEGIN_EVENT_TABLEEND_EVENT_TABLE之间的是消息处理项,比如上面的EVT_MENU(ID_MenuUser, debugWXFrame::OnCheckMenu)就是一项,我们来展开这项:

// 用户使用这个宏来定义事件映射
#define EVT_MENU(winid, func) wx__DECLARE_EVT1(wxEVT_MENU, winid, wxCommandEventHandler(func)) // 事件映射内部宏
#define wx__DECLARE_EVT2(evt, id1, id2, fn) \
wxDECLARE_EVENT_TABLE_ENTRY(evt, id1, id2, fn, NULL),
#define wx__DECLARE_EVT1(evt, id, fn) \
wx__DECLARE_EVT2(evt, id, wxID_ANY, fn) // 根据传递进来的参数创建一个wxEventTableEntry对象
#define wxDECLARE_EVENT_TABLE_ENTRY(type, winid, idLast, fn, obj) \
wxEventTableEntry(type, winid, idLast, wxNewEventTableFunctor(type, fn), obj)

那么EVT_MENU(ID_MenuUser, debugWXFrame::OnCheckMenu)展开后就得到:

wxEventTableEntry(wxEVT_MENU, winid, wxID_ANY,
wxNewEventTableFunctor(wxEVT_MENU, wxCommandEventHandler(fn)), NULL)

wxNewEventTableFunctor用于实例化一个wxObjectEventFunctor对象,wxCommandEventHandler用于强转消息处理函数,传递给wxNewEventTableFunctor使用。


#define wxCommandEventHandler(func) \
wxEVENT_HANDLER_CAST(wxCommandEventFunction, func)
#define wxEVENT_HANDLER_CAST( functype, func ) \
( wxObjectEventFunction )( wxEventFunction )wxStaticCastEvent( functype, &func ) inline wxObjectEventFunctor *
wxNewEventTableFunctor(const wxEventType& WXUNUSED(evtType),
wxObjectEventFunction method)
{
return new wxObjectEventFunctor(method, NULL);
}

这样就可以静态创建多个wxEventTableEntry对象,加入到sm_eventTableEntries中。

静态消息映射表处理过程

消息处理过程,wxEvtHandler::TryHereOnly会调用静态表查询处理:

bool wxEvtHandler::TryHereOnly(wxEvent& event)
{
// Then static per-class event tables
if ( GetEventHashTable().HandleEvent(event, this) )
return true;
}

上面GetEventHashTable方法将返回一个wxEventHashTable对象,随后调用这个对象的HandleEvent方法:

  1. wxEventHashTable根据消息类型将Table分段,这样可以快速查找到指定类型的table;
  2. 遍历指定类型的table,对每个wxEventTableEntry调用ProcessEventIfMatchesId
bool wxEventHashTable::HandleEvent(wxEvent &event, wxEvtHandler *self)
{
// Find all entries for the given event type.
wxEventType eventType = event.GetEventType();
const EventTypeTablePointer eTTnode = m_eventTypeTable[eventType % m_size];
if (eTTnode && eTTnode->eventType == eventType)
{
const wxEventTableEntryPointerArray&
eventEntryTable = eTTnode->eventEntryTable;
const size_t count = eventEntryTable.GetCount();
for (size_t n = 0; n < count; n++)
{
const wxEventTableEntry& entry = *eventEntryTable[n];
if ( wxEvtHandler::ProcessEventIfMatchesId(entry, self, event) )
return true;
}
} return false;
}

wxEvtHandler::ProcessEventIfMatchesId处理流程如下:

  1. 检查当前收到的消息是否与本项wxEventTableEntryBase匹配
  2. 调用用户的处理函数m_fn执行,这里m_fn也是经过了好几层的封装,有兴趣的继续跟踪。
bool wxEvtHandler::ProcessEventIfMatchesId(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
// 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;
(*entry.m_fn)(handler, event); if (!event.GetSkipped())
return true;
} return false;
}

动态消息映射表

动态映射表项的增加和删除是通过wxEvtHandler::DoBindwxEvtHandler::DoUnbind执行的,wxWidgets提供了多种接口执行这个操作。

需要注意的是:如果传递进wxEvtHandler对象,则在处理的时候调用用户指定的wxEvtHandler对象进行处理;如果未指定,则直接使用当前的wxEvtHandler对象进行处理。

提供类似功能的还有Connect接口,但是Connect用起来比较复杂,建议后续代码全部使用Bind接口,这里不做赘述。

Bind接口是模板函数,提供三种接口:

  1. 直接绑定全局函数,此时只有event类型和function参数必须,建议使用
  2. 绑定Funtor函数,用户需要自己用wxNewEventFunctor进行封装,不建议使用
  3. 使用类内部函数,同时需要指定类指针,建议使用

三种接口声明如下:

template <typename EventTag, typename EventArg>
void Bind(const EventTag& eventType,
void (*function)(EventArg &),
int winid = wxID_ANY,
int lastId = wxID_ANY,
wxObject *userData = NULL) template <typename EventTag, typename Functor>
void Bind(const EventTag& eventType,
const Functor &functor,
int winid = wxID_ANY,
int lastId = wxID_ANY,
wxObject *userData = NULL) 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)

动态消息映射表处理过程

与静态消息入口相同,在wxEvtHandler::TryHereOnly得到调用:

bool wxEvtHandler::TryHereOnly(wxEvent& event)
{
// Handle per-instance dynamic event tables first
if ( m_dynamicEvents && SearchDynamicEventTable(event) )
return true;
}

动态表查找比较简单,动态消息表存在m_dynamicEvents链表中,只要出去链表的每一项逐个调用ProcessEventIfMatchesId就可以了。

bool wxEvtHandler::SearchDynamicEventTable( wxEvent& event )
{
wxList::compatibility_iterator node = m_dynamicEvents->GetFirst();
while (node)
{
wxDynamicEventTableEntry *entry = (wxDynamicEventTableEntry*)node->GetData();
node = node->GetNext(); if ( event.GetEventType() == entry->m_eventType )
{
wxEvtHandler *handler = entry->m_fn->GetEvtHandler();
if ( !handler )
handler = this;
if ( ProcessEventIfMatchesId(*entry, handler, event) )
return true;
}
}
return false;
}

wxWidgets源码分析(3) - 消息映射表的更多相关文章

  1. wxWidgets源码分析(4) - 消息处理过程

    目录 消息处理过程 消息如何到达wxWidgets Win32消息与wxWidgets消息的转换 菜单消息处理 消息处理链(基于wxEvtHandler) 消息处理链(基于wxWindow) 总结 消 ...

  2. wxWidgets源码分析(8) - MVC架构

    目录 MVC架构 wxDocManager文档管理器 模板类创建文档对象 视图对象的创建 创建顺序 框架菜单命令的执行过程 wxDocParentFrame菜单入口 wxDocManager类的处理 ...

  3. 源码分析RocketMQ消息轨迹

    目录 1.发送消息轨迹流程 1.1 DefaultMQProducer构造函数 1.2 SendMessageTraceHookImpl钩子函数 1.3 TraceDispatcher实现原理 2. ...

  4. 源码分析 Kafka 消息发送流程(文末附流程图)

    温馨提示:本文基于 Kafka 2.2.1 版本.本文主要是以源码的手段一步一步探究消息发送流程,如果对源码不感兴趣,可以直接跳到文末查看消息发送流程图与消息发送本地缓存存储结构. 从上文 初识 Ka ...

  5. 源码分析Kafka 消息拉取流程

    目录 1.KafkaConsumer poll 详解 2.Fetcher 类详解 本节重点讨论 Kafka 的消息拉起流程. @(本节目录) 1.KafkaConsumer poll 详解 消息拉起主 ...

  6. Akka源码分析-Remote-收消息

    上一遍博客中,我们分析了网络链接建立的过程,一旦建立就可以正常的收发消息了.发送消息的细节不再分析,因为对于本地的actor来说这个过程相对简单,它只是创立链接然后给指定的netty网路服务发送消息就 ...

  7. Akka源码分析-Remote-发消息

    上一篇博客我们介绍了remote模式下Actor的创建,其实与local的创建并没有太大区别,一般情况下还是使用LocalActorRef创建了Actor.那么发消息是否意味着也是相同的呢? 既然ac ...

  8. 源码分析 Kafka 消息发送流程

    Futuresend(ProducerRecord<K, V> record) Futuresend(ProducerRecord<K, V> record, Callback ...

  9. wxWidgets源码分析(9) - wxString

    目录 wxString wxString的中文字符支持 Windows Linux Unicode Linux UTF-8 总结 wxString与通用字符串的转换 wxString对象的创建 将wx ...

随机推荐

  1. 【转】Kubernetes scheduler学习笔记

    简介 Kubernetes是一个强大的编排工具,可以用来很方便的管理许多台机器,为了使机器的资源利用率提高,同时也尽可能的把压力分摊到各个机器上,这个职责就是由scheduler来完成的. Kuber ...

  2. OpenStack Train版-12.创建虚拟网络并启动实例(控制节点)

    使用VMware虚拟机创建网络可能会有不可预测到的故障,可以通过dashboard界面,管理员创建admin用户的网络环境 1.第一种: 建立公共提供商网络在admin管理员用户下创建 source ...

  3. ArcGIS处理栅格数据(一)

    一.建立影像金字塔 ArcToolbox--数据管理工具--栅格--栅格属性--构建金字塔(pyramid) 说明:该方式一次只能为一张影像数据建立影像金字塔. ArcToolbox--数据管理工具- ...

  4. codevs1169传纸条 不相交路径取最大,四维转三维DP

    这个题一个耿直的思路肯定是先模拟.. 但是我们马上发现这是具有后效性的..也就是一个从(1,1)开始走,一个从(n,m)开始走的话 这样在相同的时间点我们就没法判断两个路径是否是相交的 于是在dp写挂 ...

  5. json-server All In One

    json-server All In One https://github.com/typicode/json-server#getting-started $ npm i -g json-serve ...

  6. MDN All In One

    MDN All In One https://github.com/mdn/ https://wiki.mozilla.org/MDN MDN 要凉了 https://developer.mozill ...

  7. 2016 最新的 树莓派3 Raspberry Pi 3 上手评测 图解教程 新手必看!(VNC 安装,启动,关闭)

    1.png . 官方教程: INSTALLING OPERATING SYSTEM IMAGES: https://www.raspberrypi.org/documentation/installa ...

  8. ReactDOM API All In One

    ReactDOM API All In One React DOM API render() hydrate() unmountComponentAtNode() findDOMNode() crea ...

  9. Mapbox 地图实验室

    Mapbox 地图实验室 Learn with Mapbox https://www.mapbox.com/community/education/ https://labs.mapbox.com/e ...

  10. css break-inside

    css break-inside The break-inside CSS property sets how page, column, or region breaks should behave ...