离上次发博文过去了好久,先是要忙一个机器人的项目,然后就是部门的事情和考试周复习,然后就到了考试周,趁着复习的间隙,拾起了寒假时候抄的界面库,修掉了从前的bug。
bug1 控件显示问题
当初抄这个库的时候就对排版部分的代码一头雾水,借着这次调bug,稍微理清了排版部分代码的意图。界面的排版是动态进行的,用户用placement命名空间的各种排版元素构造出整个排版的结构布局,然后调用WinForm对象的ApplyPlacement成员,把前面构造好的布局传给它,函数内部递归地读取结构对象的大小和它的子对象,一层一层的计算并应用排版布局。
如此应用排版
ApplyPlacement( HorzScale(10, 100, 0.5, Control(txtRegex), Control(txtRegex2) ) );
ApplyPlacement里面是单纯的调用的排版对象的ApplyTo函数
inline void WinContainer::ApplyPlacement(placement::Base::Pointer Placement) { Placement->ApplyTo(GetPlacement()); }
ApplyTo函数是个虚函数,根据不同的排版元素,有不同的效果,基本原理上都是计算空间,设定当前布局在窗口大小变化时的行为,递归应用子元素的排版,然后设定子元素的位置。
feature2 修改了Event的定义
这个库原版的事件是用宏来批量生成的,代码没有高亮,夹杂着各种连接token的##,显得十分晦涩难懂,不过考虑到这个库是vczh大学时期的作品,那时候还没有C++11,不能愉快的使用变长模板参数来写一个Event,选择用宏来替代也是一个不错的选择,阅读代码的人面对着这一大片宏,肯定会先感叹编写者的牛b吧。
这一次把Event替换成了vlpp里面的Event,并且由于连带的包含关系,我把智能指针类Ptr和函数对象类Func也一并抄了过来。综合对比这个库之前的Event和vlpp里的Event,两者在代码的组织上并没有太大的区别,只是vlpp条理更加清晰,把EventHandler的部分独立出来成了一个Func,并且添加了lambda等可调用对象的封装支持。
Event内部含有一个Func数组,存放,本身重载了operator()(TArgs… args),当Event本身被调用时,Func数组内的可调用对象一个一个地被调用,同时变长参数表TArgs…一个一个被完美转发进Func的调用参数内,达成了callback的目的。
void operator()(TArgs... args) const { for (auto&& handler : handlers) { handler(PerfectForward<TArgs>(args)...); } }
下面说收Func的设计,std::function相比于Func,其最大的弱点就是——不能封装成员函数指针,众所周知,成员函数指针在调用的时候是需要一个对应的对象指针的存在的,而function并不能做到这个,为此,C++还提供了std::mem_fn函数,接受对象指针和成员函数指针,返回一个可调用对象,这本身是可以的,问题是在这个对象本身和std::function并无任何关联,类型名称也是奇观的下划线开头,平常只能用auto自动推断,如果我想把std::function和这个东西生硬的结合起来,还不如自己写一个。
Func本身包含一个可调用对象的智能指针,利用operator()的重载调用智能指针中的可调用对象,而这个可调用对象是该重点说的东西:
C++有三种不同的可调用对象——普通函数指针、成员函数指针、仿函数,这三种东西可以分别封装成三个可调用对象,然后重点来了——让他们共同继承自一个相同的基类,并且这个积累有一个纯虚的operator()成员,这个时候就能解释为什么Func内是一个只能指针而不是一个对象副本了,因为没办法愉快的利用多态调用不同的可调用对象。
有了Func,有了Event,这个事件回调机制就齐了。
p.s.前几天无意中看到了boost::signal,这是一个加强版的Event-EventHandler设计,可以给事件回调进行分组管理,临时禁用某些回调,或者批量解绑回调等,之后的时间,写完了自己用的Map和List,要尝试实现一下这个。
p.p.s.顺便感叹写boost的大牛们在没有变长模板存在的时代硬生生的用模板参数堆出了最高支持9个参数的boost::signal,真是太厉害了
feature3 合并Destroy和析构函数,使用智能指针代替手动析构资源
这里又要吐槽Win32API的三个消息WM_QUIT, WM_CLOSE, WM_DESTROY了,虽然从设计角度,这三个消息并没有冲突,但是对于使用者来说,相似的事件名称的确会产生混淆。也许是为了照顾上古时期C语言不支持过长标识符的设计,我想把他们分别命名成WM_APPLICATIONQUIT, WM_ASKWINDOWCLOSE, WM_WINDOWDESTROYED会好很多,这样也能一目了然地明白三个消息的意义——程序退出,请求关闭窗口,窗口摧毁完毕
原本这个程序的消息机制是这样的,窗口接受WM_CLOSE消息,关闭自身的同时如果发现自己是主窗口,那么呼叫Application退出(此时还未从主窗口消息处理函数中退出),Application管理着程序所有的窗口和控件,Application对窗口一个一个调用Destroy函数,然后再用delete析构调他们,Destroy函数会一层一层地销毁掉对象实际占有的资源,到基类WinControl的Destroy内调用DestroyWindow时,WM_DESTROY会生成然后立即发送给消息处理函数处理,这时候,窗口还没有从Application的列表里删除,Application的消息处理函数找到列表里的窗口对象,将WM_DESTROY消息传递给他处理,这时候窗口释放掉所有的附带对象包括Timer,反注册快捷键,最后调用PostQuitMessage,程序正常退出。
将Destroy写进析构函数里之后,由于调用DestroyWindow的时候,窗口已经从Application的列表中被删除,WM_DESTROY找不到处理者,被默认处理,没有PostQuitMessage被调用,所以窗口已经没了,消息循环还在跑,程序还没有完全退出,这时候只要在负责delete所有窗口的函数的最后加上PostQuitMessage,就能正确的结束消息循环,关闭程序。
就先说到这里吧。