第二十五篇:在SOUI中做事件分发处理

不同的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)这个宏来识别那些来自本页面的事件。如果事件是来自其它页面则不处理。

时间: 2024-10-21 04:22:09

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

Egret入门学习日记 --- 第二十五篇(书中 9.16~9.17 节 内容)

第二十五篇(书中 9.16~9.17 节 内容) 对于昨天的关于 List 组件使用的问题,我打算到书中提到List之后,再回头补充. 还有就是 Scroller 的 TileLayout 布局方式,也要去研究一下. 好了,开始按照书中内容一步一步走. 开始 9.16节. 重点: 1.设定TabBar皮肤. 2.设置TabBar布局. 操作: 1.设定TabBar皮肤. 第一步,准备素材! 第二步,创建 exml 文件! 第三步,拖入组件!约束大小! 第四步,增加两个状态 down 和 up.

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

在SOUI中已经提供了大部分常用的控件,但是内置控件不可能满足用户的所有要求,因此一个真实的应用少不得还要做一些自定义控件. 学习一个新东西,最简单的办法就是依葫芦画瓢.事实上在SOUI系统中内置控件和自定义控件的开发流程是完全一样的,因此只需要打开SOUI的源代码,随便找一个控件看一下就大体差不多了. 下面我以controls.extend目录下的的SRadioBox2控件为例对控件开发过程需要注意的地方做一点说明. 要开发一个控件,首先要确定的是应该从哪个控件来继承.选择一个合适的基类是正确

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

此篇文章是基于C++宝典写的图书管理系统,本人对其中的部分做了相应修改,并且以现有格式替代原有格式,使程序更加清晰明了.此程序运行在VS2017上. 系统设计 图书管理系统分为四个模块:图书管理模块.读者管理模块.借书模块和还书模块. 总体设计 图书管理:对图书基本信息进行维护,主要包括新增图书.更改图书基本信息.删除图书.查找图书等功能模块. 读者管理:对读者的基本信息进行维护,主要包括增加读者.删除读者信息.查找特定读者等功能模块. 借书:进行图书借阅操作,该部分是整个图书管理系统中最主要的

Egret入门学习日记 --- 第二十二篇(书中 9.7~9.8 节 内容)

第二十二篇(书中 9.7~9.8 节 内容) 开始 9.7节 内容. 重点: 1.进度条ProgressBar的声明和使用. 操作: 1.进度条ProgressBar的声明和使用. 现在真的轻车熟路了,很简单.无非就是设置一下最大值,当前值的属性. 然后,事件监听的话,也是一样的.只不过事件名字的话,我就选书中这个事件吧. 可惜不能发动图,不然你们就可以看到这个进度条,每帧+1的速度前进. 当然,如果你想换自定义皮肤,还是老规矩,去找默认的 EXML 文件. 然后,怎么换素材,就按照自己喜欢的换

Egret入门学习日记 --- 第二十四篇(书中 9.12~9.15 节 内容)

第二十四篇(书中 9.12~9.15 节 内容) 开始 9.12节 内容. 重点: 1.TextInput的使用,以及如何设置加密属性. 操作: 1.TextInput的使用,以及如何设置加密属性. 创建exml文件,拖入组件,设置好id. 这是显示密码星号处理的属性. 创建绑定类. 实例化,并运行. 但是焦点在密码输入框时,密码是显示的. 暂时不知道怎么设置 “焦点在密码框上时,还是显示为 * 号” 的方法. 至此,9.12节 内容结束. 开始 9.13节 . 这个,和TextInput的使用

Egret入门学习日记 --- 第二十八篇(书中 9.19 ~ 9.19 节 内容)

第二十八篇(书中 9.19 节 内容) 没想到第九章的组件篇可真是够长的,没事,慢慢来吧. 开始 9.19节. 重点: 1.创建一个Tips提示组件. 操作: 1.创建一个Tips提示组件. 哇!出大问题!这个Tips组件有点牛皮!怎么办? 书中内容这部分,我直接懵逼. 还有这部分也是. 一点一点来分析好吧. 好,开始分析一波. 第一步:查看最终展示效果. 这是最终效果.(我去,我现在才知道,原来博客园的编辑器还可以放Gif图片...) 第二步:这个黑色的弹框组件本质上是由什么构成的? 可见,在

第二十五篇 苦逼的人生该作何解释

今天我遇到了一些问题,这是一些生活中的情感问题,要是谁有好的方法,能不能为我指点一下迷津...... 原本今天的心情就像今天的天气一样,风和日丽,景色花香,日子还是过的很潇洒,又有小伙伴们一起娱乐.一起玩耍.但是下午突如其来的 一个电话 打破了这种和谐的景象,是什么电话呢,来自我爸手机上的一条短信,大概内容就是  要 确定是否为我的学费做出担保,其中的含义我就不多说了,反正一句话很简单------要钱,于是他马上打了一通电话给我,说这是什么情况:而我爸的脾气又火爆,我虽然性子特温顺,但是放到他身

python全栈开发基础【第二十五篇】死锁,递归锁,信号量,Event事件,线程Queue

一.死锁现象与递归锁 进程也是有死锁的 所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用, 它们都将无法推进下去.此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程, 如下就是死锁 #死锁现象 死锁------------------- from threading import Thread,Lock,RLock import time mutexA = Lock() mutexB = Lock() class

第二十五篇 jQuery 学习7 获取并设置 CSS 类

jQuery 学习7 获取并设置 CSS 类 jQuery动态控制页面,那么什么是动态呢?我们就说一下静态,静态几乎又纯html+css完成,就是刷新页面之后,不会再出现什么变动,一个实打实的静态页面.那么动态,我们基于静态的特征说,动态:刷新页面之后,还可以发生样式改变等,就为动态. 这节课我们学习的是jQuery控制css,那么css样式都被改变了,算动态么?当然算啦,刷新页面之后发生了改变,就已经不是静态了,只要是变动了,就算是啦. 我们这节课学习四个控制css的方法: addClass(