• 关于UI回调Invoker的实现(二)


    上篇我说到,光有一个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<=====

    作者:若狂
    出处:http://www.cnblogs.com/tynia
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
  • 相关阅读:
    黑马程序员面向对象09天1
    一键安装LNMP
    多屏互动技术
    阿里云CentOS 64位解决kernel2.6.32220.13.1.el6.x86_64 has missing requires错误
    listview的onItemClickListener失效
    在阿里云主机上基于CentOS用vsftpd搭建FTP服务器(赚)
    asp.net关于在线支付的实现过程
    C#关闭登录窗体,显示主窗体
    winform 刷新父窗体(转)
    用代码生成器生成的DAL数据访问操作类 基本满足需求了
  • 原文地址:https://www.cnblogs.com/tynia/p/invoker_two.html
Copyright © 2020-2023  润新知