以前在用 SetTimer 设置定时器的时候,新定时器的 ID 从来都是从 0 开始递增,通过宏定义的方式。这个 ID 是我们手动设置进去的,Windows 窗口内部并不关心这个值是多少,有何意义。
然而,今天在查看 SetTimer 的文档时,有注意到,它的原型其实是这样的:
UINT_PTR WINAPI SetTimer(
HWND hWnd,
UINT_PTR nIDEvent,
UINT uElapse,
TIMERPROC lpTimerFunc
);
,它的 nIDEvent 被定义成的是 UINT_PTR,看到这个类型时,我若有所思:它应该被设置成任意一个值(当然包括指针在内)。当它是一个指针时,我们就可以将任何的上下文数据封装进结构体来传递了。于是我写了一个例子来简单验证:
#include <string>
#include <atlbase.h>
#include <atlwin.h>
class Window : public CWindowImpl<Window>
{
private:
struct TimerContext {
std::wstring msg;
};
protected:
LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) {
TimerContext* pCtx = new TimerContext;
pCtx->msg = L"Timer context message";
// 注意到,我直接把指针设置成定时器的ID
SetTimer(UINT_PTR(pCtx), 5000);
bHandled = FALSE;
return 0;
}
LRESULT OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) {
// 实际上,我们的定时器ID是一个指针值
TimerContext* pCtx = reinterpret_cast<TimerContext*>(wParam);
// 清除定时器
KillTimer(UINT_PTR(pCtx));
MessageBox(pCtx->msg.c_str());
delete pCtx;
return 0;
}
LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) {
::PostQuitMessage(0);
return 0;
}
private:
BEGIN_MSG_MAP(Window)
MESSAGE_HANDLER(WM_TIMER, OnTimer)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
END_MSG_MAP()
};
int __stdcall wWinMain(HINSTANCE hInst, HINSTANCE, LPWSTR lpCmdLine, int nShowCmd) {
Window wnd;
wnd.Create(nullptr, nullptr, nullptr, WS_OVERLAPPEDWINDOW);
wnd.ShowWindow(SW_SHOWNORMAL);
MSG msg;
while (GetMessage(&msg, nullptr, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
,这是我头一回写 MFC/ATL/WTL 的完整程序。下面是效果图:
通过代码可以看到,我把一个指针设置成了新创建定时器的 ID。在收到定时器消息之后,又转制转换回结构体指针,取得其它数据(此处的 msg string),从而达到传递额外数据的功用。
但是我估计,在 OnTimer 窗口消息回调中这样使用意义不大,因为毕竟是成员函数。然而,如果指定了 SetTimer 的最后一个参数:回调函数 时,估计意义就大了。
再然而,实际上,我几乎没有见到有人设置了 SetTimer 的回调函数。所以,是否好用,还有待挖掘、考证。
源代码:Source.cpp。