上篇我说到,光有一个IOperation*的指针,是无法记录这么多事件的。由于无法确定要把回调绑定到哪个事件上,因此,我们需要引入一个中间的传递机制。

没有看到前面的请先查阅上一篇 关于UI回调Invoker的实现(一)

问题回到,我怎么让InitControl(...)知道,我是SetPressFunc,还是SetItemChangeFunc,等等事件的回调。

我初步的想法是:

定义一个IAttacher接口:

class IAttacher
{
public:
virtual ~IAttacher() {};
virtual void AttachInvoker(IUIWnd* wnd) = 0;
};

  然后定义一个实现类:Attacher。就是把IOperation的指针,attach到窗口控件事件对应的IOperation上。

  在这个之前,我需要定义一些宏,配合模版使用。(复杂东西了,其实思路清楚了,也不是很难)。

  用宏生成一个函数(还有一个类,后面会讲到):

DECLARE_CALLBACK(SetPressFunc)

  这个宏被定义为:

#define DECLARE_CALLBACK(name)	DECLARE_DELEGATE(name, name##Attacher)

  同理,再次被定义:下面是一段非常难看的代码,见谅,鄙人刚刚毕业一年,还需要磨练。

#define DECLARE_DELEGATE(FuncName, AttcherName)																			\
template<class TObj> \
class AttcherName : public IAttacher \
{ \
public: \
AttcherName(IOperation* invoker) \
{ \
_invoker = invoker; \
} \
\
virtual void AttachInvoker(IUIWnd* wnd) \
{ \
wnd->FuncName(_invoker); \
} \
\
private: \
IOperation* _invoker; \
}; \
template<class TObj> \
inline IAttacher* FuncName(TObj* obj, void (TObj::*func)(IUIWnd*, const EventArg&))\
{ \
return new AttcherName<TObj, TWnd>(MakeInvoker(obj, func)); \
} \

  其中的 MakeInvoker 就是把一个类对象,和它的一个成员函数绑定,生成一个Invoker。不另外解释了。

  先理一下思路:

  当我在代码里面用到 SetPressFunc(this, btnTest, &CUIXXX::OnBtnTest) 的时候,编译器编译时候,就会生成  SetPressFunc<CUIXXX, IUIButton>的函数

template<class TObj>
inline IAttacher* SetPressFunc(TObj* obj, void (TObj::*func)(IUIWnd*, const EventArg&))
{
return new SetPressFuncAttcher<TObj, TWnd>(MakeInvoker(obj, func));
}

如果程序运行,就会调用到它,并且new出一个 SetPressFuncAttcher<CUIXXX, IUIButton>的对象。这个对象是由MakeInvoker生成的IOperation对象来构造的。

这个类是由宏生成出来的模板类,以上述为例,生成

template<class TObj>
class SetPressFuncAttcher: public IAttacher
{
public:
SetPressFuncAttcher(IOperation* invoker)
{
_invoker = invoker;
} virtual void AttachInvoker(IUIWnd* wnd)
{
wnd->SetPressFunc(_invoker);
} private:
IOperation* _invoker;
};

  这个类用IOperation* 来构造,然后要把IOperation对象,attach到窗口对应的响应事件的IOperation上。调用接口 AttachInvoker ,传入窗口的指针就可以了,AttachInvoker会调用到窗口wnd的SetPressFunc,并把invoker对象指针传入。

  OK,到这里,大部分的问题已经有解决办法了。

  因此,上一篇中说到的InitControl的最后一个参数,IOperation* 就不再有用了。我们换成:

template<class TObj, class TWnd>
inline void InitControl(TObj* obj, TWnd*& wnd, IUIWnd* parentWnd, const std::wstring& id, IAttacher* attacher)
{
InitControl(obj, wnd, parentWnd, id.c_str());
attacher->AttachInvoker(wnd);
}

  这样,attcher调用 AttachInvoker,传入btnTest窗口指针, invoker被顺利attach到btnTest上,点击这个btnTest的时候,就会调用到CUIXXX::OnBtnTest(IUIWnd*, const EventArg&)了。

  其实,这引入了另外一个问题:

  仔细观察 SetPressFuncAttcher的 AttachInvoker 接口,这个接口传入的参数是IUIWnd* 类型的。因为这个是接口,不能声明为模板(原因,请查看C++PL的模板参数推导部分。还是简单说一下原因:如果用模板函数做接口,对于编译器来说,遇到不同类型的调用,就要生成对应的接口,往虚表里面插入。)

  对于Button来说,Button 继承自 CUIWnd 和 IUIButton,CUIWnd 虚继承了 IUIWnd,而IUIButton虚继承了IUIWnd。(为什么用虚继承,就不用说原因了吧)。

对与SetPressFunc这个接口,应该存在于 IUIButton,而不应该存在于IUIWnd中。类似的还有SetItemChangeFunc应该存在于 IComboBox,而不是IUIWnd,等等。

  如果我们这样写按照上面的这样写,想编译通过,必须IUIWnd也具有SetPressFunc,SetItemChangeFunc……等等一大串本应该是控件窗口才有的成员。

  正确的静态类图如下,我只列出了一个控件的及事件响应函数的示例。

  

而根据上面完成的代码,想要编译成功,类图必须是这样的

下面这个图看起来很头疼,IUIWnd拥有了所有控件窗口各种事件响应的接口:既拥有Button的SetPressFunc,又拥有ComboBox的SetItemChangeFunc,还拥有某些特殊控件的的SetRightClickFunc。但是对于我的Button来说,我仅仅需要里面的SetPressFunc,于是我重写自己需要的SetPressFunc,来覆盖掉CUIWnd的SetPressFunc的实现。

  而对于CUIWnd来说,因为继承自IUIWnd,CUIWnd需要实现一遍所有的事件响应,即使不做任何事情,也需要return NULL。

  兴许对于某些人,这些可以接受。但是这是我写的库,我怎么能忍受这种乱套的逻辑?

  必须找一个方法改。

  目前的完成的UI库到这里了。引擎部分的绘制,还没有开始写。但是我会找时间,把这个Invoker的问题解决。

  感谢你看到这里。

  从上述的代码中,其实还有一个隐藏得很深的问题:

  IAttacher 只是一个中间对象,用来把invoker和窗口控件关联起来。关联完成之后,就没有用了。而invoker却是个有用的东西,因为某个按钮,在不同的状态下,可能有不同的行为。例如在奇数次点击时候,响应FuncA,在偶数次点击的时候,响应FuncB。invoker就需要不断地切换,因此,我写了一个简单内存池管理invoker。但是对于IAttacher对象,我不知道怎么用一个办法来管理,如果一个用户要不断地调用 宏生成的 SetPressFunc(或者宏生成的其他类似的SetXXXFunc),每次都会new一个SetXXXFuncAttacher<TObj>对象。而这个对象,用于InitControl中把wnd和invoker关联上。如果不用share_ptr<>,该怎么删除IAttacher对象?

  如果有好的办法,请务必通知我。

=====>THE END<=====

关于UI回调Invoker的实现(二)的更多相关文章

  1. 关于UI回调Invoker的实现(一)

    打算写一个DirectUI库,在写其中底层窗口的回调构造的时候遇到一个问题. Invoker是一个模板,因为closure的关系,它必须保存一个类对象的指针,和回调函数的地址.而函数调用的时候,就可以 ...

  2. iOS开发-UI 从入门到精通(二)

    iOS开发-UI 从入门到精通(二)是对 iOS开发-UI 从入门到精通(一)知识点的巩固,主要以习题练习为主,增强实战经验,为以后做开发打下坚实的基础! ※开发环境和注意事项: 1.前期iOS-UI ...

  3. iOS开发UI篇—Quartz2D简单使用(二)

    iOS开发UI篇—Quartz2D简单使用(二) 一.画文字 代码: // // YYtextview.m // 04-写文字 // // Created by 孔医己 on 14-6-10. // ...

  4. AngularJs的UI组件ui-Bootstrap分享(二)——Collapse

    Collapse折叠控件使用uib-collapse指令 <!DOCTYPE html> <html ng-app="ui.bootstrap.demo" xml ...

  5. Android常见UI组件之ListView(二)——定制ListView

    Android常见UI组件之ListView(二)--定制ListView 这一篇接上篇.展示ListView中选择多个项及实现筛选功能~ 1.在位于res/values目录下的strings.xml ...

  6. Hybrid框架UI重构之路:二、事出有因

    上文回顾:Hybird框架UI重构之路:一.师其长技以自强 一切的重构都是有原因的,或许为了更快速度.更好体验.更快捷开发等,于是就有了自己的开发目标,简单看看未重构前UI("中国移动式&q ...

  7. Android UI开发第三十二篇——Creating a Navigation Drawer

    Navigation Drawer是从屏幕的左侧滑出,显示应用导航的视图.官方是这样定义的: The navigation drawer is a panel that displays the ap ...

  8. UI Recorder 安装教程(二)

    前言: UI Recorder支持无线native app(Android, iOS)录制, 基于macaca实现:https://macacajs.com/ 本次教程只针对无线native app( ...

  9. iOS 8 UI布局 AutoLayout及SizeClass(二)

    一.新特性Size Class介绍 随着iOS8系统的公布,一个全新的页面UI布局概念出现,这个新特性将颠覆包含iOS7及之前版本号的UI布局方式,这个新特性就是Size Class. Size Cl ...

随机推荐

  1. Problem G: 沉迷字符的WJJ (LCS)

    Contest - 河南省多校连萌(四) Problem G: 沉迷字符的WJJ Time Limit: 1 Sec  Memory Limit: 128 MBSubmit: 6  Solved: 5 ...

  2. “全栈2019”Java多线程第十一章:线程优先级详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  3. JAVA中Date类的使用

    一. Date类 Date类对象的创建: 1.创建一个当前时间的Date对象 //创建一个代表系统当前日期的Date对象 Date d = new Date(); 2.创建一个我们指定的时间的Date ...

  4. kali linux之msf后渗透阶段

    已经获得目标操作系统控制权后扩大战果 提权 信息收集 渗透内网 永久后门 基于已有session扩大战果 绕过UAC限制 use exploit/windows/local/ask set sessi ...

  5. js判断手机端操作系统的两种方法

    //判断手机端操作系统(Andorid/IOS),并自动跳转相应下载界面 androidURL ="http://xxx/xxx.apk"; var browser = { ver ...

  6. 爬虫实战4:用selenium爬取淘宝美食

    方案1:一次性爬取全部淘宝美食信息 1. spider.py文件如下 __author__ = 'Administrator' from selenium import webdriver from ...

  7. day00 预习 ------基础数据类型预习 ,int ,str ,bool ,dict ,set ,切片,等相关

    知识点明确 1 int 2 str 3 元祖 4.列表 5. 字典 6 集合 7 布尔 1  int  数据类型 int 数据类型指的是. 数字型的内容 ,主要用于计算, 2 str 字符类型 str ...

  8. python3的全局变量和局部变量

    局部变量 定义在函数体内部的变量称为局部变量 函数的形参也是局部变量 局部变量的作用范围只在声明该局部变量的函数体内 局部变量在函数调用时被创建,在函数调用完成后自动销毁 全局变量 定义在函数体外,模 ...

  9. FlowPortal-BPM——离线审批(邮箱审批)配置

    一.将系统文件复制到安装目录下 二.以管理员身份运行bat安装程序 三.开启邮件离线审批服务 四.创建数据库表(JAVA数据库 或 .Net数据库) 五.配置config文件(发件箱.收件箱.错误问题 ...

  10. L08-Linux解决Device eth0 does not seem to be present,delaying initialization问题

    问题前提: 在VirtualBox中克隆Linux服务器,如下,由Centos6.5_Base克隆得到node01服务器,采用的是完全克隆的方式,克隆时重新初始化MAC地址. 原服务器Centos6. ...