8.inherited
在讲继承的时候我们说过,若映射的回调函数返回false,就继续遍历下一个mapslot链表:
class A : public wabc::wndbase
{
WABC_DECLARE_MSG_MAP()
public:
// ...
bool on_keydown(wabc::msg_keydown &msg)
{
// ...
return true;
}
};
class B : public A
{
WABC_DECLARE_MSG_MAP()
public:
// ...
bool on_keydown(wabc::msg_keydown &msg)
{
// ...
return false;
}
};
在B窗口里,按了一个键,会先触发B::on_keydown,等B::on_keydown执行完了,再触发A::on_keydown。若有这样的需求,在B::on_keydown里,先执行A::on_keydown的代码,等A::on_keydown执行完了,再继续B::on_keydown剩余的代码,该如何做。一种可能的实现是:
bool B::on_keydown(wabc::msg_keydown &msg)
{
A::on_keydown(msg);
// ...
return true;
}
这种实现的弊端在于:1.必须知道mapslotB的下一个节点是mapslotA;2.A::on_keydown必须是public或protected。若消息映射机制提供一种方式,先执行下一个链表的映射,再回到原先的地方继续执行,这会好很多。我们为msg增加一个inherited函数,实现下一个映射链表节点的回调:
bool B::on_keydown(wabc::msg_keydown &msg)
{
msg.inherited();
// ...
return true;
}
inherited实现的关键点在于wndproc::process_WM函数。在wndproc::process_WM函数里,我们现在的代码,是从mapslot链表的第一个节点开始遍历:
void wndproc::process_WM(wndbase &wnd, msg_struct &msg)
{
// ...
mapslot_node *pSlot= wnd.m_mapslot_head->next;
// ...
}
若pSlot指向的是我们需要的节点,这问题就解决了。在msg里增加两个变量:wnd和cur_slot,就很容易实现msg.inherited()功能。
struct msg_node
{
wndbase *wnd;
mapslot_node * cur_slot;
// ...
bool inherited();
};
增加后,wndproc::process_WM将变成:
void wndproc::process_WM(wndbase &wnd, msg_struct &msg)
{
// ...
mapslot_node *pSlot= msg.cur_slot;
// ...
}
由于msg_node增加了一个变量增加了wnd变量,所以process_WM第一个参数就不需要了,同理,其它process_XXX也一样。
class wndproc
{
static bool process_WM_CREATE(msg_struct &msg);
static bool process_WM(msg_struct &msg);
static bool process(msg_struct &msg);
friend msg_node;
public:
static LRESULT CALLBACK WndProc(...);
};
增加一个process函数,里面放的是WndProc主逻辑的代码。
bool wndproc::process(msg_struct &msg)
{
struct special_msg
{
typedef bool (*fun_t)(msg_struct &);
UINT message;
fun_t fun;
};
static special_msg items[] = {
WM_CREATE, &wndproc::process_WM_CREATE,
};
const size_t nSize = countof(items);
size_t first = 0, last = nSize, n = nSize, m, tmp;
#ifdef _DEBUG
for (m = 1; m < countof(items); ++m)
{
assert(items[m - 1].message < items[m].message);
}
#endif
while (0 < n)
{
m = n / 2;
tmp = first + m;
if (items[tmp].message < msg.message)
{
first = tmp + 1;
n -= m + 1;
}
else if (msg.message < items[tmp].message)
n = m;
else
return items[tmp].fun(msg);
}
return process_WM(msg);
}
LRESULT CALLBACK wndproc::WndProc(...)
{
msg_struct msg={0};
// ...
if(msg.wnd)
{
msg.cur_slot= msg.wnd->m_mapslot_head->next;
if(process(msg))
return msg.result;
}
return ::DefWindowProc(hWnd, message, wParam, lParam);
}
这样,一切又回到原先的功能上,所作的改动,是为了实现msg_struct::inherited的功能。至此,已经万事俱备,水到渠成。
bool msg_node::inherited()
{
mapslot *p = static_cast<mapslot*>(cur_slot);
if(p->next != p)
{
msg_struct tmp = static_cast<msg_struct &>(*this);
tmp.cur_slot= p->next;
return wndproc::process(tmp);
}
return false;
}
inherited是msg_node的成员函数,不能在msg_node上定义,原因在于msg_struct存在很多“变异体”,若在msg_struct上定义,这会导致在所有的变异体都定义一次。这过程是乏味的且代码重复。
对于WM_PAINT消息,有必要增加对inherited的支持。在讲继承的时候说过,对于WM_PAINT的映射消息,只能返回true,以避免对BeginPaint和EndPaint的重复调用。在调用inherited的时候,依然要避免这种情况。WM_PAINT消息的wParam和lParam没有意义,我们可以利用这两个参数,来判断BeginPaint和EndPaint是否被调用了。
bool map_paint::on_map(const msgmap_t &a, void * x, msg_struct &msg)
{
struct paint_sentry : PAINTSTRUCT
{
HWND m_hWnd;
HDC hdc;
paint_sentry(HWND hWnd) : m_hWnd(hWnd)
{
hdc = ::BeginPaint(m_hWnd, this);
}
~paint_sentry()
{
::EndPaint(m_hWnd, this);
}
};
msgmap_t &o= *reinterpret_cast<msgmap_t *>(x);
typedef bool (msgmap_t::*fun_t)(HDC, const RECT &, msg_paint &);
const fun_t &f = *reinterpret_cast<const fun_t *>(&a.on_message);
if(msg.wParam == 0 && msg.lParam == 0)
{
paint_sentry ps(msg.hWnd);
msg.wParam = reinterpret_cast<WPARAM>(hdc);
msg.lParam = reinterpret_cast<LPARAM>(&ps.rcPaint);
(o.*f)(ps.hdc, ps.rcPaint, reinterpret_cast<msg_paint &>(msg));
}
else
{
HDC dc = reinterpret_cast<HDC>(msg.wParam);
const RECT &rt = *reinterpret_cast<RECT *>(msg.lParam);
(o.*f)(hdc, rt, reinterpret_cast<msg_paint &>(msg));
}
return true;
}
msg_struct::inherited的操作,只会遍历mapslot链表里的映射,若没有找到对应的映射函数,将一直遍历到链表尾,DefWindowProc api不会被调用。
msg_struct::inherited的操作,是一把双刃剑,很强大,但却可能造成一些意外。原因在于,有可能无法判断下一个映射函数的行为。若不能正确判断下一个映射函数执行完后对inherited()后面的代码造成的影响,这时候可能就会有一些奇怪的问题。调用msg.inherited(),一定要明白这么做的意义。
总结:
在有些语言里,在语法里提供了inherited的支持,我们在这里提供了一个模拟的实现,花费的代价不大,但在使用上却十分便捷。
请点击这里下载'wabc'库的最终源码。