消息映射表

消息是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. P1714 切蛋糕 单调队列

    题目: 题目描述 今天是小Z的生日,同学们为他带来了一块蛋糕.这块蛋糕是一个长方体,被用不同色彩分成了N个相同的小块,每小块都有对应的幸运值. 小Z作为寿星,自然希望吃到的第一块蛋糕的幸运值总和最大, ...

  2. 【noi 2.6_162】Post Office(DP)

    这题和"山区建小学"除了输入不同,其他都一样.(解析可见我的上一篇随笔) 但是,这次我对dis[][]加了一个优化,画一下图就可明白. 1 #include<cstdio&g ...

  3. Caocao's Bridges HDU - 4738 找桥

    题意: 曹操在赤壁之战中被诸葛亮和周瑜打败.但他不会放弃.曹操的军队还是不擅长打水仗,所以他想出了另一个主意.他在长江上建造了许多岛屿,在这些岛屿的基础上,曹操的军队可以轻易地攻击周瑜的军队.曹操还修 ...

  4. 牛客编程巅峰赛S1第6场 - 黄金&钻石&王者 A.牛牛爱奇数 (模拟)

    题意:有一组数,每次将所有相等的偶数/2,求最少操作多少次使得所有数变成奇数. 题解:用桶标记,将所有不同的偶数取出来,然后写个while模拟统计一下次数就行. 代码: class Solution ...

  5. 错误: 未能完成程序集的安装(hr = 0x8007000b)。探测终止。

     解决方案:VS中"工具"->"选项"->"Web项目"->"对网站和项目使用IIS Express的64位版& ...

  6. Redis 持久化(Persistence)

    作为内存数据库,Redis 依然提供了持久化机制,其主要目的有两个: 安全:保证进程崩溃后数据不会丢失 备份:方便数据迁移与快速恢复 Redis 同时提供两种持久化机制: RDB 快照:数据库在某个时 ...

  7. test markdown && 代码高亮

    #include<cstdio> #include<cstring> #include<queue> #include<vector> #include ...

  8. Leetcode(884)-索引处的解码字符串

    给定一个编码字符串 S.为了找出解码字符串并将其写入磁带,从编码字符串中每次读取一个字符,并采取以下步骤: 如果所读的字符是字母,则将该字母写在磁带上. 如果所读的字符是数字(例如 d),则整个当前磁 ...

  9. hdu 6242 Geometry Problem

    Geometry Problem Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 262144/262144 K (Java/Other ...

  10. sqlmap 详解

    sqlmap 使用总结   0x01 需要了解 当给 sqlmap 这么一个 url 的时候,它会:1.判断可注入的参数 2.判断可以用那种 SQL 注入技术来注入 3.识别出哪种数据库 4.根据用户 ...