不同的SOUI控件可以产生不同的事件。SOUI系统中提供了两种事件处理方式:事件订阅 + 事件处理映射表(参见第八篇:SOUI中控件事件的响应)

事件订阅由于直接将事件及事件处理函数连接,不存在事件分发的问题,这里主要介绍使用事件映射表时的事件分发。

在回答这个问题前,首先了解一下什么是事件分发。

在大型项目中,程序逻辑可能非常复杂,如果将所有UI中控件的事件处理集中在一个消息/事件映射表里,代码的可维护性会变得非常差。解决这个问题常见的方法就是将事件进行分类(如根据来源分类),不同类别的事件采用一个独立的事件处理对象来处理,这就是事件分发的核心。

目前流行的UI通常采用Tab控件来组织UI,不同的功能放到不同的Tab页中,不同的Tab页可能互不相干的功能模块,对于类似这样的情形很自然的会想到采用事件分发机制来实现模块之间逻辑的解耦(如下图中SoTool采用的UI)。

在上面的UI中,虽然整个UI被TAB分成了6个页面,但是6个页面都存在于同一个宿主窗口中。

一般情况下,如果UI相对比较简单,我们推荐直接在宿主窗口的事件处理映射表中统一处理控件事件。

但是当出现如上图这样复杂的界面时,最好是将不同功能页的事件处理在不同的对象中分别处理。

在MFC中,一个类要处理消息,这个类通常派生自CCmdTarget(可能记错了,太久不用MFC了),主窗口收到的消息会自动路由到这个消息处理对象中。

在WTL中,WTL提供了一组消息映射宏:CHAIN_MSG_MAP,CHAIN_MSG_MAP_MEMBER等以便将消息分发到同样实现了消息映射表的任意C++对象。

SOUI的事件分发采用了WTL消息分发类似的机制,同样采用事件映射宏的方式来构造事件映射表,下面是SOUI中几个主要的和事件分发相关的宏:

#define EVENT_MAP_BEGIN()                           \
protected: \
virtual BOOL _HandleEvent(SOUI::EventArgs *pEvt)\
{ \
UINT uCode = pEvt->GetID(); \ #define EVENT_MAP_DECLEAR() \
protected: \
virtual BOOL _HandleEvent(SOUI::EventArgs *pEvt);\ #define EVENT_MAP_BEGIN2(classname) \
BOOL classname::_HandleEvent(SOUI::EventArgs *pEvt)\
{ \
UINT uCode = pEvt->GetID(); \ #define EVENT_MAP_END() \
return __super::_HandleEvent(pEvt); \
} \ #define EVENT_MAP_BREAK() \
return FALSE; \
} \ #define CHAIN_EVENT_MAP(ChainClass) \
if(ChainClass::_HandleEvent(pEvt)) \
return TRUE; \ #define CHAIN_EVENT_MAP_MEMBER(theChainMember) \
{ \
if(theChainMember._HandleEvent(pEvt)) \
return TRUE; \
} #define EVENT_CHECK_SENDER_ROOT(pRoot) \
{ \
SWindow *pWnd = sobj_cast<SWindow>(pEvt->sender);\
if(!pWnd->IsDescendant(pRoot)) \
return FALSE; \
} // void OnEvent(EventArgs *pEvt)
#define EVENT_HANDLER(cd, func) \
if(cd == uCode) \
{ \
func(pEvt); return TRUE; \
}

下面是SoTool中的MainDlg中的事件处理:

    //soui消息
EVENT_MAP_BEGIN()
EVENT_NAME_COMMAND(L"btn_close", OnClose)
EVENT_NAME_COMMAND(L"btn_min", OnMinimize)
EVENT_NAME_COMMAND(L"btn_max", OnMaximize)
EVENT_NAME_COMMAND(L"btn_restore", OnRestore)
CHAIN_EVENT_MAP_MEMBER(m_imgMergerHandler)
CHAIN_EVENT_MAP_MEMBER(m_codeLineCounter)
CHAIN_EVENT_MAP_MEMBER(m_2UnicodeHandler)
CHAIN_EVENT_MAP_MEMBER(m_folderScanHandler)
CHAIN_EVENT_MAP_MEMBER(m_calcMd5Handler)
EVENT_MAP_END()

上面代码中,EVENT_MAP_BEGIN()和EVENT_MAP_END()这两个宏构造出一个空的事件处理函数,该函数自动将未处理的事件交给基类的事件处理函数处理。

如果基类中没有事件处理函数,显然这个事件映射表编译不能通过,此时SOUI提供了另一个EVENT_MAP_BREAK()来代替。

上面的事件分发表中,我使用CHAIN_EVENT_MAP_MEMBER宏将来自不同页面的控件事件传递到不同的事件处理对象中。

下面代码是m_imgMergerHandler对象头文件。

class CImageMergerHandler : public IFileDropHandler
{
friend class CMainDlg;
public:
CImageMergerHandler(void);
~CImageMergerHandler(void); void OnInit(SWindow *pRoot); void AddFile(LPCWSTR pszFileName);
protected:
virtual void OnFileDropdown(HDROP hDrop); void OnSave();
void OnClear();
void OnModeHorz();
void OnModeVert(); EVENT_MAP_BEGIN()
EVENT_CHECK_SENDER_ROOT(m_pPageRoot)
EVENT_NAME_COMMAND(L"btn_save", OnSave)
EVENT_NAME_COMMAND(L"btn_clear", OnClear)
EVENT_NAME_COMMAND(L"radio_horz", OnModeHorz)
EVENT_NAME_COMMAND(L"radio_vert", OnModeVert)
EVENT_MAP_BREAK() SWindow *m_pPageRoot;
SImgCanvas *m_pImgCanvas;
};

可以看到这里的事件映射表使用了EVENT_MAP_BREAK来结束。

在SOUI中推荐使用控件的name属性来标识一个控件(name属性是一个wchar*的字符串,使用name虽然在事件分发时采用字符串比较,较基于整数id属性的比较效率低一点,好处在于代码的可读性好),不同的页面中的控件如果出现相同的name该如何识别呢?

在SOUI中使用了一点小技巧:在事件处理对象中实现一个oninit函数,该函数在maindlg中处理WM_INITDIALOG时被调用,在oninit中保存了一个页面根节点的指针:

SWindow *m_pPageRoot;

在事件映射表的开始,我们采用EVENT_CHECK_SENDER_ROOT(m_pPageRoot)这个宏来识别那些来自本页面的事件。如果事件是来自其它页面则不处理。

第二十五篇:在SOUI中做事件分发处理的更多相关文章

  1. 第二十八篇:SOUI中自定义控件开发过程

    在SOUI中已经提供了大部分常用的控件,但是内置控件不可能满足用户的所有要求,因此一个真实的应用少不得还要做一些自定义控件. 学习一个新东西,最简单的办法就是依葫芦画瓢.事实上在SOUI系统中内置控件 ...

  2. 第二十五篇 -- C++宝典中的图书管理系统

    此篇文章是基于C++宝典写的图书管理系统,本人对其中的部分做了相应修改,并且以现有格式替代原有格式,使程序更加清晰明了.此程序运行在VS2017上. 系统设计 图书管理系统分为四个模块:图书管理模块. ...

  3. Egret入门学习日记 --- 第十五篇(书中 6.1~6.9节 内容)

    第十五篇(书中 6.1~6.9节 内容) 好的,昨天完成了第五章. 今天来看第六章. 总结重点: 1.如何对组件进行分组? 跟着做: 重点1:如何对组件进行分组? 首先,选中你想要组合的组件. 然后点 ...

  4. SpringBoot非官方教程 | 第二十五篇:2小时学会springboot

    转载请标明出处: http://blog.csdn.net/forezp/article/details/61472783 本文出自方志朋的博客 一.什么是spring boot Takes an o ...

  5. Python之路(第二十五篇) 面向对象初级:反射、内置方法

    [TOC] 一.反射 反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问.检测和修改它本身状态或行为的一种能力(自省).这一概念的提出很快引发了计算机科学领域关于应用反射性的研究.它 ...

  6. flask第二十五篇——控制语句

    有兴趣的请加船长公众号:自动化测试实战 先和大家强调一个发邮件的问题 # coding: utf-8 import smtplib from email.mime.text import MIMETe ...

  7. 第二十五篇:使用 sigaction 函数实现可靠信号

    前言 在前文中,讲述了一个可靠信号的示例.它分成几个步骤组成( 请参考前文 ).在 Linux 系统编程中,有个方法可以将这些步骤给集成起来,让我们使用起来更加的方便. 那就是调用 sigaction ...

  8. 第二十五篇 hashlib模块(* *)

    用于加密相关的操作,Python 3.x里代替了md5模块和sha模块,主要提供 SHA1, SHA224, SHA256, SHA384, SHA512 ,MD5 算法. 加密复杂程度: SHA1  ...

  9. SpringBoot第二十五篇:SpringBoot与AOP

    作者:追梦1819 原文:https://www.cnblogs.com/yanfei1819/p/11457867.html 版权声明:本文为博主原创文章,转载请附上博文链接! 引言   作者在实际 ...

随机推荐

  1. 全局对象的构造函数会在main 函数之前执行

    #include <iostream> using namespace std; class A { public: A() { cout << "Generator ...

  2. Java集合中Map接口的使用方法

    Map接口 Map提供了一种映射关系,其中的元素是以键值对(key-value)的形式存储的,能够实现根据key快速查找value: Map中的键值对以Entry类型的对象实例形式存在: 建(key值 ...

  3. C# Winfrom 页面传值

    2个窗体 Parent,Children 代码: Parent public partial class Parent : Form { public string parentValue = &qu ...

  4. static 修饰内部类

    static一般用来修饰成员变量或函数也修饰代码块,一般不能修饰类,但是可以修饰内部类,被修饰的内部类可以直接作为一个普通类来用,不需要创建一个外部类的实例,而普通内部类的引用需要创建一个外部类的实例 ...

  5. Struts2常用标签

    Struts2常用标签总结 一 介绍 1.Struts2的作用 Struts2标签库提供了主题.模板支持,极大地简化了视图页面的编写,而且,struts2的主题.模板都提供了很好的扩展性.实现了更好的 ...

  6. 比较两个mysql数据库表结构的差异

    需求来源:一个线上系统,一个开发系统,现在要把开发系统更新到线上,但是开发系统的数据库结构与线上的略有差异,所以需要找出两个数据库的表结构差异. 数据库表结构的差异 注:操作均在Linux系统下完成 ...

  7. jsp中<!DOCTYPE>标签

    今天写代码时遇到一个问题,定义了如下一个样式: .c_c1:hover td { background-color: #edf5ce;} <tr class="c_c1"&g ...

  8. IOS - 消息推送原理和实现

    一.消息推送原理: 在实现消息推送之前先提及几个于推送相关概念,如下图1-1: 1.Provider:就是为指定IOS设备应用程序提供Push的服务器,(如果IOS设备的应用程序是客户端的话,那么Pr ...

  9. Java实现文件复制的四种方式

    背景:有很多的Java初学者对于文件复制的操作总是搞不懂,下面我将用4中方式实现指定文件的复制. 实现方式一:使用FileInputStream/FileOutputStream字节流进行文件的复制操 ...

  10. cell分割线宽度不满屏处理

    if ([cell respondsToSelector:@selector(setSeparatorInset:)]) { [cell setSeparatorInset:UIEdgeInsetsZ ...