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


    打算写一个DirectUI库,在写其中底层窗口的回调构造的时候遇到一个问题。

    Invoker是一个模板,因为closure的关系,它必须保存一个类对象的指针,和回调函数的地址。而函数调用的时候,就可以用一个通用的接口invoke就行了。

    接口的声明如下:

    class IOperation
    {
    public:
    	virtual ~IOperation() {};
    	virtual void Invoke(IUIWnd* wnd, const EventArg& arg) = 0;
    };
    

      回调函数调用的时候,调用Invoke(...),传入(谁)那个窗口调用的,和调用的可能需要的数据。因为回调函数可能有不同的参数,为了方便,我把所有的回调都封装成参数相同的函数。数据都被封装到EventArg结构里面,这里我们不讨论EventArg。

    Invoker类继承了IOperation这个接口,声明如下:  

    template<class TObj>
    class Invoker : public IOperation
    {
    public:
    	typedef void (TObj::*TFunc)(IUIWnd*, const EventArg&);
    	struct closure
    	{
    		TObj*	_Obj;
    		TFunc	_Func;
    	};
    
    	Invoker(TObj* obj, TFunc func)
    	{
    		_closure._Obj = obj;
    		_closure._Func = func;
    	}
    
    	Invoker(const Invoker& ci)
    	{
    		_closure._Obj = ci._closure._Obj;
    		_closure._Func = ci._closure._Func;
    	}
    
    	bool Equel(TObj* obj, TFunc func)
    	{
    		if (_closure._Obj == obj && _closure._Func == func)
    		{
    			return true;
    		}
    		return false;
    	}
    
    	virtual void Invoke(IUIWnd* wnd, const EventArg& arg)
    	{
    		(_closure._Obj->*_closure._Func)(wnd, arg);
    	}
    
    private:
    	closure	_closure;
    };
    

      里面有个closure,这个就不多讲了,博客园某博主翻译过一篇,写得很好:点击这里查看。感谢博主@Jans的翻译。

      从invoker类可以很明了的看到,怎么去构造一个Invoker(再重复一遍,回调函数是一个类的成员函数,因此,在类外调用的时候,需要一个类成员的对象(或对象指针),和成员函数的地址。)

      假设现在有一个button,有一个操作是要相应按下的操作。

    virtual IOperation* SetPressFunc( IOperation* oper ) = 0;

      我的思路是,先构造一个Invoker<TObj> invoker(obj, func), 然后把invoker的指针赋给button中响应按下操作的IOperation指针。

      假设有个button对象:btnOK, 于是就用 btnOK->SetPressFunc(invoker); 的确工作了。还算不错。

      但是有个小问题:btnOK必须是已经初始化完成了的,也就是说,已经 new 出来了。说实话,我可不想让库的使用者自己去调用 new Button()。竟然库是我写的,我必须用库管理好这些窗口(控件)。我会在程序初始化,读窗口配置文件的时候,为程序员用户,new好这些窗口;在程序结束的时候,释放掉。

      然后这也引出了一个问题:窗口都是库来初始化的,程序员用户想让按钮A被点击的时候,相应自己写的回调函数。作为用户,我都不知道A的对象在哪,叫什么名字,这怎么把对调绑定到这个按钮上?真这样了,用户肯定要骂我。程序员骂人都是很透彻的,代码里面fuck不少,你懂的。我需要写一个让你定义的窗口指针,关联到你想关联的窗口对象上。

      现在整理一下,如果我是用户,我当前所有的资源。

      1、界面配置文件是我用UI编辑器写的,每个窗口(控件)都有一个唯一(准确地说,是父窗口下唯一的)标识吧,比方说,枚举ID(MFC就是这么干的),或者名字。

      2、类对象,和要回调成员函数的地址。用于生成一个invoker;

      OK,就这么多,够了。

      我写了一个关联的函数 InitControl:

    template<class TWnd, class TObj>
    void InitControl(TObj* obj, TWnd*& out, const std::wstring& id)
    {
    	InitControl(obj, out, obj->getMainWnd(), id);
    }
    

      又是模板 = =%。。好吧,其实这个模板函数里面调用的InitControl,还是个模板。这些程序员用户不用管,我现在只是说明原理。如果写完了,开源之后,你们再去研究代码。

      别骂我,你想想啊,你的一个类,肯定是一个界面吧,一个程序可能有各种界面吧。我不能为每个类对象去写一个InitControl吧。嘿嘿。要付出代价的。

      InitControl参数说明:第一个,类对象指针;第二个,需要关联的窗口指针;第三个参数,要关联的窗口的id,字符串表示哈。

      比方说,我CUIXXX类下面,有一个button,声明是IUIButton* btnTest;界面配置文件里面,我想关联的对象的id是btnTest;

      我关联的时候,就 InitControl (this, btnTest, L"btnTest"); 然后btnTest就跟我想关联的按钮关联上了。

      现在我想让它顺便把按下的响应函数也加上去。好吧,我要改InitControl了。

    template<class TWnd, class TObj>
    void InitControl(TObj* obj, TWnd*& out, const std::wstring& id,IOperation* oper)
    {
    	InitControl(obj, out, obj->getMainWnd(), id);
            //....
    }    

      很显然,这样是不行的。

      IOperation只是记录了一个类对象和类成员函数的地址,以及调用所执行的操作。

      一个窗口有鼠标左键单击事件,可能有鼠标右键单击事件,以及其它事件:例如,editbox可能有TextChange事件,Combobox有ItemChange事件,就单个IOperation是无法记录这么多事件的,因此,我们需要引入一个中间的传递机制。

      把问题留在这里。下一篇我们再讲。

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

    作者:若狂
    出处:http://www.cnblogs.com/tynia
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
  • 相关阅读:
    用一条UPDATE语句交换两列的值
    js之事件冒泡和事件捕获详细介绍
    C# String与string的区别
    setTimeout()和setInterval()方法的区别?
    jQuery的.bind()、.live()和.delegate()之间区别
    在Hyper-V虚拟机中使用Wi-Fi上网
    调整框架的尺寸,是否显示滚动条,跳出框架
    调整框架的尺寸
    查看jquery绑定的事件函数
    Onload,Onunload,onbeforeunload,$(window).load(function() {})和$(document).ready(function(){})
  • 原文地址:https://www.cnblogs.com/tynia/p/invoker_one.html
Copyright © 2020-2023  润新知