最近在一个程序里需要使用到属性表,程序都写完了,运行才发现这个烂玩意竟然每次都显示在屏幕的左上角。如果使用 MFC 或者 ATL 写程序那就简单了,因为它们各自的机制都能保证在窗口显示之前就实现正确的消息派发路由,可苦了像我这样的 SDK 爱好者。
我既不愿意像 MFC 那样挂什么 CBT 钩子,也不会因此而像 ATL 一样弄什么 Thunk,上网上搜一下先。结果发现这竟然还真是个问题,结果发现还真有人去挂钩子了,唉,杀鸡焉用牛刀!
看了一下 MSDN,其中说创建属性表的时候可以指定回调函数,大喜过望,在其中试着 SetWindowPos 了一把,结果什么用也没有,不知为何。想来想去,只好祭出老汉的惯用法宝——子类化!这个玩意儿厉害,只要你愿意,可以把窗口搞它个生活不能自理。
基本思路是,在属性表的回调里先子类化,处理 WM_SHOWWINDOW 消息时将窗口居中,然后反子类化。
1、创建属性表示不要忘了添加使用回调函数的标志,并指定回调函数。
1 2 |
psh.dwFlags = YourOtherFlags | PSH_USECALLBACK; psh.pfnCallback = PropSheetCallback; |
2、回调函数如下实现。
1 2 3 4 5 6 |
WNDPROC propSheetProcOld = NULL; // 旧的窗口过程 void CALLBACK PropSheetCallback(HWND hwnd, UINT uMsg, LPARAM lParam) { if(uMsg == PSCB_INITIALIZED) propSheetProcOld = (WNDPROC)SetWindowLong(hwnd, GWL_WNDPROC, (LONG)PropSheetProc); } |
3、新的窗口过程如下实现,其中的 CenterWindow 函数请抄袭 ATL 在 CWindow 类中的实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
LRESULT CALLBACK PropSheetProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { if(uMsg == WM_SHOWWINDOW && wParam) { CenterWindow(hwnd, NULL); LRESULT lRet = CallWindowProc(propSheetProcOld, hwnd, uMsg, wParam, lParam); SetWindowLong(hwnd, GWL_WNDPROC, (LONG)propSheetProcOld); return lRet; } return CallWindowProc(propSheetProcOld, hwnd, uMsg, wParam, lParam); } |
此方法在 PSDK 提供的例子 Property 中实验通过。