win32gui之event handling分析
hanlray@hotmail.com
Revision: 1.0 Date: 2005/07/17
1. 前言
win32gui是一个符合C++精神的GUI框架,源自在C++届颇有声望的cuj杂志上的一系列文章,其充分发挥了C++语言的特点,有助于提高C++在GUI开发方面的框架水平。其feature很多,现列举几个:
real GUI RAIIno more message mapseasy handling of events/ event handlingthread-safetySTL-friendly
在实际使用过程中,发现其event handling机制很独特,下面就对它进行一下分析。
2. easy handling of events
熟悉MFC,包括WTL、wxWindow开发的朋友对它们的消息映射一定不会陌生,要处理一条消息,一般需要三个步骤:
定义消息映射,把消息和消息处理函数关联起来。这一般是用某种形式的宏来实现的。在窗口类的头文件中声明该消息处理函数。在窗口类的源文件中实现该消息处理函数
虽然可能有wizard的帮助,这种过程仍然是繁琐的,也是有缺点的:
易出错。比如我想处理WM_PAINT消息,虽然我定义了ON_WM_PAINT宏,但是消息处理函数却写成了OnPaint1,这时编译器是不会 有任何错误或警告的,当然也不会调到你的这个OnPaint1函数里。换句话说,你的消息处理函数必须写得和CWnd类里定义的一模一样,如果不小心写错 了,你是不会收到任何警告的。甚至没有消息映射而只有消息处理函数,编译器也是无法发觉的。看似magic的宏的大量使用,实际上减弱了代码的可读性。增加、删除或修改消息映射通常会带来相应头文件的改变,从而增加编译时间。
win32gui是怎么做的呢?假设有一个窗口类child_view,其源文件为child_view.cpp,对其进行事件处理的步骤如下:
在child_view.cpp中定义一个负责事件处理的类,比如叫做child_view_hanlder:
struct child_view_handler : event_handler<child_view_handler, child_view> {
};
直接写消息处理函数。比如要处理的消息是WM_ERASEBKGND,就这样写:
struct child_view_handler : event_handler<child_view_handler, child_view> {
handle_event on_erase_bg(wm::draw::erase_bg_arg arg) {
...//some code here
return event<WM_ERASEBKGND>().HANDLED_BY(&me::on_erase_bg);
}
};
注意该函数名是可以任取的。
这样的过程也许不比MFC等简单多少,但其优点也是突出的:
实现对某个消息的处理只需要一个消息处理函数,而且该消息处理函数的名称是任意的,这就大大减少了出错的机会。窗口类和消息处理类是分离的,这意味改变消息处理逻辑不会影响窗口类本身。
至少从表面上看,这里是没有消息映射的,最后的那个return语句看起来有点像,但它在消息处理函数体里,总不会在执行消息处理函数的时候定义消息映射吧?当然也 不会是消息处理函数名称,因为它的名称是任取的。就算win32gui对Win32 GUI封装的再好,也不可能脱离win32窗口系统的原理:系统肯定会向这个 child_view窗口发送一条WM_ERASEBKGND消息,要对该消息进行处理,是一定需要该消息与消息处理函数的对应关系的,不管该关系用什么方式表达。 其实关键就是在那个return语句上,它显然可以是一种消息映射的表达,只是位置有点奇怪而已。其中的HANDLED_BY是一个宏,其定义为
#define HANDLED_BY(f) get_proxy(f).handled_by<f>()
先看event类模板的定义:
template<int wm> struct event {
template<class self_type>
detail::event_keeper0<wm,self_type> get_proxy( handle_event (self_type::*)() ) {
return detail::event_keeper0<wm,self_type>();
}
template<class self_type, class p1>
detail::event_keeper1<wm,self_type,p1> get_proxy( handle_event (self_type::*)(p1) ) {
return detail::event_keeper1<wm,self_type,p1>();
}
template<class self_type, class p1, class p2>
detail::event_keeper2<wm,self_type,p1,p2> get_proxy( handle_event (self_type::*)(p1,p2) ) {
return detail::event_keeper2<wm,self_type,p1,p2>();
}
template<class self_type, class p1, class p2, class p3>
detail::event_keeper3<wm,self_type,p1,p2,p3> get_proxy( handle_event (self_type::*)(p1,p2,p3) ) {
return detail::event_keeper3<wm,self_type,p1,p2,p3>();
}
template<class self_type, class p1, class p2, class p3, class p4>
detail::event_keeper4<wm,self_type,p1,p2,p3,p4> get_proxy( handle_event (self_type::*)(p1,p2,p3,p4) ) {
return detail::event_keeper4<wm,self_type,p1,p2,p3,p4>();
}
template<class self_type, class p1, class p2, class p3, class p4, class p5>
detail::event_keeper5<wm,self_type,p1,p2,p3,p4,p5> get_proxy( handle_event (self_type::*)(p1,p2,p3,p4,p5) ) {
return detail::event_keeper5<wm,self_type,p1,p2,p3,p4,p5>();
}
template<class self_type, class p1, class p2, class p3, class p4, class p5, class p6>
detail::event_keeper6<wm,self_type,p1,p2,p3,p4,p5,p6> get_proxy( handle_event (self_type::*)(p1,p2,p3,p4,p5,p6) ) {
return detail::event_keeper6<wm,self_type,p1,p2,p3,p4,p5,p6>();
}
};
其有多个成员含函数模板,分别对应消息处理函数的参数个数从0到6的情况。这里我们的消息处理函数只需要一个参数,因此get_proxy返回的是实例属于 detail::event_keeper1类模板,再看event_keeper1的定义:
template<int wm, class me, class p1> struct event_keeper1 {
template<handle_event (me::*func)(p1)> handle_event handled_by() {
return event_adder<wm, me, func1_caller<me,p1,func> >();
}
};
其返回的是event_adder类模板的实例:
template<int wm, class me, class func> struct event_adder : handle_event {
event_adder() {
if ( detail::initialize_static_trick() ) {
assert(false); // should never happen
s_adder = event_adder_impl<wm,me,func>();
}
}
static event_adder_impl<wm,me,func> s_adder;
};
可以看到,event_adder的构造函数好像也没做什么,关键在于s_adder这个static成员上,看看其类型定义:
template<int wm, class me, class func> struct event_adder_impl : register_possible_dialog<me> {
typedef typename func::first_arg first_arg;
event_adder_impl() {
WIN32GUI_LOG("adding event " << wm << " for " << typeid(me).name());
if ( initialize_static_trick() ) {
// force the compiler to generate the constructor for the 'me' class
new me;
// v1.5+
// extra safety for the first argument...
typedef typename ignore_enhanced_msg<first_arg>::arg func_first_arg;
int first_argument_and_return_DONOT_match_please_check_again = check_event<wm,func_first_arg>( (func_first_arg*)0);
}
// note: for the same message, we might have multiple functions
me::s_events()[ (UINT)wm].push_back( &func::call );
}
};
真相大白了!最后一句清楚地表明,这里是有消息映射的!实际上win32gui对于每个event handler类型,在内部都为其维护了一个消息映射表,使用的数据结构是stl::map。消息处理函数的return语句在运行时实际什么也没做,它的存在只是为了让编译器产生event_adder_impl的静态变量,在这些 静态变量的初始化时刻,其构造函数被调用,从而建立消息映射关系。
3. 结束语
虽然win32gui号称no more message maps,但从上分析,其还是存在消息映射的,这也是由win32窗口系统本身决定的;只不过其表达方式有些特别,利用了模板和静态变量技术,较之MFC之类的方式,其优点也是相当明显的。