UIBase.cpp 54 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243
  1. #include "StdAfx.h"
  2. #include "UIBase.h"
  3. #include "UIManager.h"
  4. #ifdef _DEBUG
  5. #include <shlwapi.h>
  6. #pragma comment(lib, "shlwapi.lib")
  7. #endif
  8. namespace DuiLib {
  9. // WM_TOUCH 消息 ID 在 winuser.h 里受 `#if _WIN32_WINNT >= 0x0601` 保护,
  10. // 而 DuiLib 工程的 _WIN32_WINNT 兜底为 XP(0x0501),直接引用会编译失败,这里自行声明。
  11. #ifndef WM_TOUCH
  12. #define WM_TOUCH 0x0240
  13. #endif
  14. // 注:WM_POINTER* 常量统一放在 UIManager.cpp 的 PointerMsg 命名空间里(那边才是处理方),
  15. // 本文件不再重复定义 —— 原先为"模态期间屏蔽 owner 输入"定义的 DUI_WM_POINTER_FIRST/LAST
  16. // 已随 IsModalBlockedInputMessage 一并删除。
  17. namespace {
  18. typedef BOOL (WINAPI* PFN_REGISTER_TOUCH_WINDOW)(HWND, ULONG);
  19. // 与 SDK tagTOUCHINPUT 布局一致的私有副本(同样受 _WIN32_WINNT 保护,不能直接用)
  20. struct TRACE_TOUCHINPUT
  21. {
  22. LONG x;
  23. LONG y;
  24. HANDLE hSource;
  25. DWORD dwID;
  26. DWORD dwFlags;
  27. DWORD dwMask;
  28. DWORD dwTime;
  29. ULONG_PTR dwExtraInfo;
  30. DWORD cxContact;
  31. DWORD cyContact;
  32. };
  33. typedef BOOL (WINAPI* PFN_GET_TOUCH_INPUT_INFO)(HANDLE, UINT, TRACE_TOUCHINPUT*, int);
  34. void RegisterTouchWindowIfAvailable(HWND hWnd)
  35. {
  36. if (hWnd == NULL) return;
  37. HMODULE hUser32 = ::GetModuleHandle(_T("user32.dll"));
  38. if (hUser32 == NULL) return;
  39. PFN_REGISTER_TOUCH_WINDOW pfnRegister =
  40. reinterpret_cast<PFN_REGISTER_TOUCH_WINDOW>(::GetProcAddress(hUser32, "RegisterTouchWindow"));
  41. if (pfnRegister != NULL)
  42. pfnRegister(hWnd, 0);
  43. }
  44. // 【已移除】RefreshTouchRegistration(Unregister + Register 强制重算触摸归属)。
  45. //
  46. // 它曾是"假模态 + WM_TOUCH 贪婪归属权"时代的补丁:试图用注销/重注册把触摸归属权
  47. // 从 owner 重新结算到弹框头上。实测无效(弹框依然零 WM_TOUCH),且真模态方案下
  48. // 完全没必要 —— 对话框窗口是新 HWND,创建时 RegisterTouchWindowIfAvailable 注册一次即可,
  49. // 系统自己就把触摸投给它。留着只会带来 C4505(未引用函数)警告,故整段删除。
  50. // 诊断:WM_TOUCH 究竟落在哪个窗口上(弹框自己?还是被禁用/被挡住的 owner?)。
  51. // ⚠️ 只 peek,绝不调用 CloseTouchInputHandle:句柄必须交给 CPaintManagerUI::OnTouchInput 关闭。
  52. void TraceTouchArrival(HWND hWnd, WPARAM wParam, LPARAM lParam)
  53. {
  54. HMODULE hUser32 = ::GetModuleHandle(_T("user32.dll"));
  55. if (hUser32 == NULL) return;
  56. PFN_GET_TOUCH_INPUT_INFO pfnGet =
  57. reinterpret_cast<PFN_GET_TOUCH_INPUT_INFO>(::GetProcAddress(hUser32, "GetTouchInputInfo"));
  58. int x = 0, y = 0;
  59. DWORD flags = 0;
  60. UINT cInputs = LOWORD(wParam);
  61. TRACE_TOUCHINPUT ti;
  62. ::ZeroMemory(&ti, sizeof(ti));
  63. if (pfnGet != NULL && cInputs > 0 && pfnGet((HANDLE)lParam, 1, &ti, (int)sizeof(ti)))
  64. {
  65. x = (int)(ti.x / 100);
  66. y = (int)(ti.y / 100);
  67. flags = ti.dwFlags;
  68. }
  69. DuiLib::TouchTrace::Log(_T("WNDPROC-TOUCH hwnd=%p owner=%p parent=%p enabled=%d visible=%d pt=%d,%d flags=0x%X inputs=%u modal=%p fg=%p"),
  70. (void*)hWnd, (void*)::GetWindow(hWnd, GW_OWNER), (void*)::GetParent(hWnd),
  71. (int)::IsWindowEnabled(hWnd), (int)::IsWindowVisible(hWnd),
  72. x, y, (unsigned)flags, cInputs,
  73. (void*)DuiLib::CPaintManagerUI::GetModalWindow(), (void*)::GetForegroundWindow());
  74. }
  75. // ---------------------------------------------------------------------------
  76. // 【临时全量诊断】记录系统到底把哪些"输入类消息"发给了哪个窗口。
  77. //
  78. // 背景:弹框打开后,日志里既没有 WM_TOUCH(任何窗口都没有),也没有 WM_LBUTTONDOWN/UP,
  79. // 却能看到弹框收到 WM_MOUSEMOVE(hover)。这说明触摸可能被系统降级成了鼠标模拟,
  80. // 或者走了 WM_GESTURE / WM_POINTER* 另外两条通道。这三个通道目前都没被记录到,
  81. // 因此需要一段覆盖面更广的诊断来确定"消息到底有没有发出来、发给了谁"。
  82. //
  83. // 覆盖:WM_TOUCH(0x0240) / WM_GESTURE(0x0119) / WM_POINTER*(0x0245~0x025F 及 0x0246/0x0247 变体)
  84. // WM_MOUSEMOVE(0x0200) / WM_LBUTTONDOWN(0x0201) / WM_LBUTTONUP(0x0202)
  85. // WM_NCMOUSEMOVE(0x00A0) / WM_MOUSEACTIVATE(0x0021) / WM_SETCURSOR(0x0020)
  86. //
  87. // ⚠️ 纯只读诊断:不改消息流向、不返回值、不消费句柄。只为定位问题,验证完请删除。
  88. // ---------------------------------------------------------------------------
  89. // MSG-ID 常量(不引用任何 WM_* 宏,避免 _WIN32_WINNT 版本段差异导致编译不过)
  90. const UINT DUI_MSG_WM_TOUCH = 0x0240;
  91. const UINT DUI_MSG_WM_GESTURE = 0x0119;
  92. const UINT DUI_MSG_WM_GESTURENOTIFY = 0x011A;
  93. const UINT DUI_MSG_WM_MOUSEMOVE = 0x0200;
  94. const UINT DUI_MSG_WM_LBUTTONDOWN = 0x0201;
  95. const UINT DUI_MSG_WM_LBUTTONUP = 0x0202;
  96. const UINT DUI_MSG_WM_LBUTTONDBLCLK = 0x0203;
  97. const UINT DUI_MSG_WM_RBUTTONDOWN = 0x0204;
  98. const UINT DUI_MSG_WM_RBUTTONUP = 0x0205;
  99. const UINT DUI_MSG_WM_NCMOUSEMOVE = 0x00A0;
  100. const UINT DUI_MSG_WM_NCLBUTTONDOWN = 0x00A1;
  101. const UINT DUI_MSG_WM_MOUSEACTIVATE = 0x0021;
  102. const UINT DUI_MSG_WM_SETCURSOR = 0x0020;
  103. const UINT DUI_MSG_WM_NCHITTEST = 0x0084;
  104. const UINT DUI_MSG_WM_ACTIVATE = 0x0006;
  105. const UINT DUI_MSG_WM_ACTIVATEAPP = 0x001C;
  106. const UINT DUI_MSG_WM_SETFOCUS = 0x0007;
  107. const UINT DUI_MSG_WM_KILLFOCUS = 0x0008;
  108. void TraceInputMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
  109. {
  110. LPCTSTR pszName = NULL;
  111. switch (uMsg)
  112. {
  113. // ⚠️ case 必须是"编译期常量表达式"。这些常量在匿名 namespace 里是 const UINT
  114. // 且已初始化,属于 ICE,可以安全用作 case 标签。
  115. case DUI_MSG_WM_TOUCH: pszName = _T("WM_TOUCH"); break;
  116. case DUI_MSG_WM_GESTURE: pszName = _T("WM_GESTURE"); break;
  117. case DUI_MSG_WM_GESTURENOTIFY: pszName = _T("WM_GESTURENOTIFY"); break;
  118. case DUI_MSG_WM_MOUSEMOVE: pszName = _T("WM_MOUSEMOVE"); break;
  119. case DUI_MSG_WM_LBUTTONDOWN: pszName = _T("WM_LBUTTONDOWN"); break;
  120. case DUI_MSG_WM_LBUTTONUP: pszName = _T("WM_LBUTTONUP"); break;
  121. case DUI_MSG_WM_LBUTTONDBLCLK: pszName = _T("WM_LBUTTONDBLCLK"); break;
  122. case DUI_MSG_WM_RBUTTONDOWN: pszName = _T("WM_RBUTTONDOWN"); break;
  123. case DUI_MSG_WM_RBUTTONUP: pszName = _T("WM_RBUTTONUP"); break;
  124. case DUI_MSG_WM_NCMOUSEMOVE: pszName = _T("WM_NCMOUSEMOVE"); break;
  125. case DUI_MSG_WM_NCLBUTTONDOWN: pszName = _T("WM_NCLBUTTONDOWN"); break;
  126. case DUI_MSG_WM_MOUSEACTIVATE: pszName = _T("WM_MOUSEACTIVATE"); break;
  127. case DUI_MSG_WM_SETCURSOR: pszName = _T("WM_SETCURSOR"); break;
  128. case DUI_MSG_WM_NCHITTEST: pszName = _T("WM_NCHITTEST"); break;
  129. case DUI_MSG_WM_ACTIVATE: pszName = _T("WM_ACTIVATE"); break;
  130. case DUI_MSG_WM_ACTIVATEAPP: pszName = _T("WM_ACTIVATEAPP"); break;
  131. case DUI_MSG_WM_SETFOCUS: pszName = _T("WM_SETFOCUS"); break;
  132. case DUI_MSG_WM_KILLFOCUS: pszName = _T("WM_KILLFOCUS"); break;
  133. default: break;
  134. }
  135. // 覆盖 WM_POINTER* 一段(0x0245~0x0260,不同 SDK 版本消息号有差异,统一按范围判断)
  136. const bool bPointerMsg = (uMsg >= 0x0245 && uMsg <= 0x0260);
  137. if (pszName == NULL && !bPointerMsg) return;
  138. // WM_MOUSEMOVE / WM_NCMOUSEMOVE / WM_NCHITTEST / WM_SETCURSOR 极其频繁,
  139. // 全量记录会瞬间刷爆日志,这里每类限流 300ms。
  140. const bool bNoisy = (uMsg == DUI_MSG_WM_MOUSEMOVE || uMsg == DUI_MSG_WM_NCMOUSEMOVE
  141. || uMsg == DUI_MSG_WM_NCHITTEST || uMsg == DUI_MSG_WM_SETCURSOR);
  142. if (bNoisy)
  143. {
  144. static DWORD s_dwLastNoisyTick = 0;
  145. static UINT s_uLastNoisyMsg = 0;
  146. const DWORD dwNow = ::GetTickCount();
  147. if (s_uLastNoisyMsg == uMsg && (LONG)(dwNow - s_dwLastNoisyTick) < 300) return;
  148. s_dwLastNoisyTick = dwNow;
  149. s_uLastNoisyMsg = uMsg;
  150. }
  151. DWORD dwExtra = (DWORD)(DWORD_PTR)::GetMessageExtraInfo();
  152. DuiLib::TouchTrace::Log(_T("INPUT-TRACE target=%p msg=0x%04X %s w=0x%08X l=0x%08X extra=0x%08X fromtouch=%d modal=%p fg=%p enable=%d"),
  153. (void*)hWnd, uMsg, pszName != NULL ? pszName : _T("WM_POINTER*"),
  154. (unsigned)wParam, (unsigned)lParam, (unsigned)dwExtra,
  155. (int)((dwExtra & 0xFFFFFF80) == 0xFF515700),
  156. (void*)DuiLib::CPaintManagerUI::GetModalWindow(),
  157. (void*)::GetForegroundWindow(),
  158. (int)::IsWindowEnabled(hWnd));
  159. }
  160. // 【已移除】LogProcessWindowList / EnumProc_LogWnd(本进程顶层窗口清单,MODAL-WNDLIST)。
  161. // 它们是排查"WM_TOUCH 究竟投给了哪个陌生 HWND"的临时诊断,只被已删除的假模态消息循环调用。
  162. // 真模态方案下弹框自己就是触摸目标,这份清单不再需要,删除以免 C4505。
  163. // ---------------------------------------------------------------------------
  164. // 真模态(DialogBox)辅助:按 paint window 找到对应的 CPaintManagerUI。
  165. //
  166. // 用途:ShowModal 里要把 manager 从旧 HWND 迁到对话框新 HWND,需要先找到它。
  167. // 遍历 CPaintManagerUI::GetPaintManagers(),比对 GetPaintWindow()。
  168. // ---------------------------------------------------------------------------
  169. DuiLib::CPaintManagerUI* FindManagerByPaintWindow(HWND hWnd)
  170. {
  171. if (hWnd == NULL) return NULL;
  172. CStdPtrArray* pManagers = DuiLib::CPaintManagerUI::GetPaintManagers();
  173. if (pManagers == NULL) return NULL;
  174. for (int i = 0; i < pManagers->GetSize(); ++i)
  175. {
  176. DuiLib::CPaintManagerUI* pManager = static_cast<DuiLib::CPaintManagerUI*>(pManagers->GetAt(i));
  177. if (pManager != NULL && pManager->GetPaintWindow() == hWnd) return pManager;
  178. }
  179. return NULL;
  180. }
  181. // ---------------------------------------------------------------------------
  182. // [WM_TOUCH 归属权] "补调原窗口过程"的中转函数。
  183. //
  184. // 为什么需要它:
  185. // WM_TOUCH 不是被系统直接投递的。系统先发一条"特殊标记的 WM_MOUSEMOVE"
  186. // (开了 raw input 时是 WM_INPUT),只有这条消息走到 DefWindowProc 里,
  187. // DefWindowProc 才会把驻留的 WM_TOUCH "翻译"并派发出来
  188. // (来源:The Witness 作者的逆向结论;MSDN《Architectural Overview》的
  189. // greedy 段落也印证了这套投递模型)。
  190. // 本库的 CPaintManagerUI::MessageHandler 为了不让"触摸提升出来的鼠标移动"
  191. // 破坏拖拽/悬浮状态机,会吞掉它(return true),从而跳过了 DefWindowProc
  192. // ⇒ 窗口(尤其 #32770 真模态弹框,它依赖 DefDlgProc→DefWindowProc 这条链)
  193. // 后续永远收不到 WM_TOUCH。
  194. //
  195. // 这个中转函数由 CPaintManagerUI::RelayDefWindowProc 回调,
  196. // 作用就是"把消息喂给原窗口过程一次",补上被跳过的那次副作用。
  197. // 用静态变量保存"当前转发目标":因为回调签名不带 this,
  198. // 而每个窗口过程在同一时刻只有一个绑定关系(主窗口 or 某个弹框)。
  199. // 真模态切换 HWND 时通过 SetDefProcRelay 重新绑定,不会串。
  200. //
  201. // ⚠️ 位置要求:必须定义在 __DialogWndProc 之前(它比 __WndProc 先出现)。
  202. // ---------------------------------------------------------------------------
  203. WNDPROC g_pfnRelayTarget = NULL;
  204. HWND g_hWndRelayBound = NULL;
  205. LRESULT CALLBACK RelayWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
  206. {
  207. // 只有"当前正在转发的窗口"才有效:避免悬垂到已销毁窗口的过程上。
  208. if (g_pfnRelayTarget == NULL || g_hWndRelayBound != hWnd)
  209. return 0;
  210. return ::CallWindowProc(g_pfnRelayTarget, hWnd, uMsg, wParam, lParam);
  211. }
  212. // 把"原窗口过程"登记为转发目标(绑定到指定 HWND)。
  213. void BindRelayTarget(WNDPROC pfnOldProc, HWND hWnd)
  214. {
  215. g_pfnRelayTarget = pfnOldProc;
  216. g_hWndRelayBound = hWnd;
  217. }
  218. // DetachPaintWindow 之后、AttachPaintWindow 之前,用这个取出"待挂载的 manager"。
  219. // 因为此时 manager 已从 m_aPreMessages 摘除,FindManagerByPaintWindow 找不到它了,
  220. // 所以用一个文件级静态变量中转(只在 ShowModal 的单线程窗口过程里使用,无需同步)。
  221. static DuiLib::CPaintManagerUI* g_pDetachedManager = NULL;
  222. void SetDetachedManager(DuiLib::CPaintManagerUI* pManager) { g_pDetachedManager = pManager; }
  223. DuiLib::CPaintManagerUI* GetDetachedManager() { return g_pDetachedManager; }
  224. // 【已移除】IsModalBlockedInputMessage —— 假模态时代的"在消息循环里丢弃发给 owner 的输入消息"。
  225. // 现在 ShowModal 走真模态(DialogBoxIndirectParam),系统对话框管理器自带 owner 禁用,
  226. // 这条兜底不但多余,还会误伤:真模态期间 owner 本就需要收到 WM_ACTIVATE/WM_KILLFOCUS 之类的
  227. // 通知消息,无差别丢弃会让 owner 的状态机错乱。故整段删除,不再保留。
  228. }
  229. /////////////////////////////////////////////////////////////////////////////////////
  230. //
  231. //
  232. void UILIB_API DUI__Trace(LPCTSTR pstrFormat, ...)
  233. {
  234. #ifdef _DEBUG
  235. TCHAR szBuffer[2048] = {0};
  236. va_list args;
  237. va_start(args, pstrFormat);
  238. _vsntprintf(szBuffer, 2048, pstrFormat, args);
  239. va_end(args);
  240. CDuiString strMsg = szBuffer;
  241. strMsg += _T("\n");
  242. OutputDebugString(strMsg.GetData());
  243. #endif
  244. }
  245. LPCTSTR DUI__TraceMsg(UINT uMsg)
  246. {
  247. #define MSGDEF(x) if(uMsg==x) return _T(#x)
  248. MSGDEF(WM_SETCURSOR);
  249. MSGDEF(WM_NCHITTEST);
  250. MSGDEF(WM_NCPAINT);
  251. MSGDEF(WM_PAINT);
  252. MSGDEF(WM_ERASEBKGND);
  253. MSGDEF(WM_NCMOUSEMOVE);
  254. MSGDEF(WM_MOUSEMOVE);
  255. MSGDEF(WM_MOUSELEAVE);
  256. MSGDEF(WM_MOUSEHOVER);
  257. MSGDEF(WM_NOTIFY);
  258. MSGDEF(WM_COMMAND);
  259. MSGDEF(WM_MEASUREITEM);
  260. MSGDEF(WM_DRAWITEM);
  261. MSGDEF(WM_LBUTTONDOWN);
  262. MSGDEF(WM_LBUTTONUP);
  263. MSGDEF(WM_LBUTTONDBLCLK);
  264. MSGDEF(WM_RBUTTONDOWN);
  265. MSGDEF(WM_RBUTTONUP);
  266. MSGDEF(WM_RBUTTONDBLCLK);
  267. MSGDEF(WM_SETFOCUS);
  268. MSGDEF(WM_KILLFOCUS);
  269. MSGDEF(WM_MOVE);
  270. MSGDEF(WM_SIZE);
  271. MSGDEF(WM_SIZING);
  272. MSGDEF(WM_MOVING);
  273. MSGDEF(WM_GETMINMAXINFO);
  274. MSGDEF(WM_CAPTURECHANGED);
  275. MSGDEF(WM_WINDOWPOSCHANGED);
  276. MSGDEF(WM_WINDOWPOSCHANGING);
  277. MSGDEF(WM_NCCALCSIZE);
  278. MSGDEF(WM_NCCREATE);
  279. MSGDEF(WM_NCDESTROY);
  280. MSGDEF(WM_TIMER);
  281. MSGDEF(WM_KEYDOWN);
  282. MSGDEF(WM_KEYUP);
  283. MSGDEF(WM_CHAR);
  284. MSGDEF(WM_SYSKEYDOWN);
  285. MSGDEF(WM_SYSKEYUP);
  286. MSGDEF(WM_SYSCOMMAND);
  287. MSGDEF(WM_SYSCHAR);
  288. MSGDEF(WM_VSCROLL);
  289. MSGDEF(WM_HSCROLL);
  290. MSGDEF(WM_CHAR);
  291. MSGDEF(WM_SHOWWINDOW);
  292. MSGDEF(WM_PARENTNOTIFY);
  293. MSGDEF(WM_CREATE);
  294. MSGDEF(WM_NCACTIVATE);
  295. MSGDEF(WM_ACTIVATE);
  296. MSGDEF(WM_ACTIVATEAPP);
  297. MSGDEF(WM_CLOSE);
  298. MSGDEF(WM_DESTROY);
  299. MSGDEF(WM_GETICON);
  300. MSGDEF(WM_GETTEXT);
  301. MSGDEF(WM_GETTEXTLENGTH);
  302. static TCHAR szMsg[10];
  303. ::wsprintf(szMsg, _T("0x%04X"), uMsg);
  304. return szMsg;
  305. }
  306. /////////////////////////////////////////////////////////////////////////////////////
  307. //
  308. //
  309. //////////////////////////////////////////////////////////////////////////
  310. //
  311. DUI_BASE_BEGIN_MESSAGE_MAP(CNotifyPump)
  312. DUI_END_MESSAGE_MAP()
  313. static const DUI_MSGMAP_ENTRY* DuiFindMessageEntry(const DUI_MSGMAP_ENTRY* lpEntry,TNotifyUI& msg )
  314. {
  315. CDuiString sMsgType = msg.sType;
  316. CDuiString sCtrlName = msg.pSender->GetName();
  317. const DUI_MSGMAP_ENTRY* pMsgTypeEntry = NULL;
  318. while (lpEntry->nSig != DuiSig_end)
  319. {
  320. if(lpEntry->sMsgType==sMsgType)
  321. {
  322. if(!lpEntry->sCtrlName.IsEmpty())
  323. {
  324. if(lpEntry->sCtrlName==sCtrlName)
  325. {
  326. return lpEntry;
  327. }
  328. }
  329. else
  330. {
  331. pMsgTypeEntry = lpEntry;
  332. }
  333. }
  334. lpEntry++;
  335. }
  336. return pMsgTypeEntry;
  337. }
  338. bool CNotifyPump::AddVirtualWnd(CDuiString strName,CNotifyPump* pObject)
  339. {
  340. if( m_VirtualWndMap.Find(strName) == NULL )
  341. {
  342. m_VirtualWndMap.Insert(strName.GetData(),(LPVOID)pObject);
  343. return true;
  344. }
  345. return false;
  346. }
  347. bool CNotifyPump::RemoveVirtualWnd(CDuiString strName)
  348. {
  349. if( m_VirtualWndMap.Find(strName) != NULL )
  350. {
  351. m_VirtualWndMap.Remove(strName);
  352. return true;
  353. }
  354. return false;
  355. }
  356. bool CNotifyPump::LoopDispatch(TNotifyUI& msg)
  357. {
  358. const DUI_MSGMAP_ENTRY* lpEntry = NULL;
  359. const DUI_MSGMAP* pMessageMap = NULL;
  360. #ifndef UILIB_STATIC
  361. for(pMessageMap = GetMessageMap(); pMessageMap!=NULL; pMessageMap = (*pMessageMap->pfnGetBaseMap)())
  362. #else
  363. for(pMessageMap = GetMessageMap(); pMessageMap!=NULL; pMessageMap = pMessageMap->pBaseMap)
  364. #endif
  365. {
  366. #ifndef UILIB_STATIC
  367. ASSERT(pMessageMap != (*pMessageMap->pfnGetBaseMap)());
  368. #else
  369. ASSERT(pMessageMap != pMessageMap->pBaseMap);
  370. #endif
  371. if ((lpEntry = DuiFindMessageEntry(pMessageMap->lpEntries,msg)) != NULL)
  372. {
  373. goto LDispatch;
  374. }
  375. }
  376. return false;
  377. LDispatch:
  378. union DuiMessageMapFunctions mmf;
  379. mmf.pfn = lpEntry->pfn;
  380. bool bRet = false;
  381. int nSig;
  382. nSig = lpEntry->nSig;
  383. switch (nSig)
  384. {
  385. default:
  386. ASSERT(FALSE);
  387. break;
  388. case DuiSig_lwl:
  389. (this->*mmf.pfn_Notify_lwl)(msg.wParam,msg.lParam);
  390. bRet = true;
  391. break;
  392. case DuiSig_vn:
  393. (this->*mmf.pfn_Notify_vn)(msg);
  394. bRet = true;
  395. break;
  396. }
  397. return bRet;
  398. }
  399. void CNotifyPump::NotifyPump(TNotifyUI& msg)
  400. {
  401. ///遍历虚拟窗口
  402. if( !msg.sVirtualWnd.IsEmpty() ){
  403. for( int i = 0; i< m_VirtualWndMap.GetSize(); i++ ) {
  404. if( LPCTSTR key = m_VirtualWndMap.GetAt(i) ) {
  405. if( _tcsicmp(key, msg.sVirtualWnd.GetData()) == 0 ){
  406. CNotifyPump* pObject = static_cast<CNotifyPump*>(m_VirtualWndMap.Find(key, false));
  407. if( pObject && pObject->LoopDispatch(msg) )
  408. return;
  409. }
  410. }
  411. }
  412. }
  413. ///
  414. //遍历主窗口
  415. LoopDispatch( msg );
  416. }
  417. //////////////////////////////////////////////////////////////////////////
  418. ///
  419. CWindowWnd::CWindowWnd() : m_hWnd(NULL), m_OldWndProc(::DefWindowProc), m_bSubclassed(false), m_bUnicode(false), m_bFakeModal(false), m_bDoModal(false), m_nModalRet(0), m_pfnOldDlgWndProc(NULL), m_rcModalTarget()
  420. {
  421. }
  422. HWND CWindowWnd::GetHWND() const
  423. {
  424. return m_hWnd;
  425. }
  426. UINT CWindowWnd::GetClassStyle() const
  427. {
  428. return 0;
  429. }
  430. LPCTSTR CWindowWnd::GetSuperClassName() const
  431. {
  432. return NULL;
  433. }
  434. CWindowWnd::operator HWND() const
  435. {
  436. return m_hWnd;
  437. }
  438. void CWindowWnd::EnableUnicode()
  439. {
  440. m_bUnicode = true;
  441. }
  442. HWND CWindowWnd::CreateDuiWindow( HWND hwndParent, LPCTSTR pstrWindowName,DWORD dwStyle /*=0*/, DWORD dwExStyle /*=0*/ )
  443. {
  444. return Create(hwndParent,pstrWindowName,dwStyle,dwExStyle,0,0,0,0,NULL);
  445. }
  446. HWND CWindowWnd::Create(HWND hwndParent, LPCTSTR pstrName, DWORD dwStyle, DWORD dwExStyle, const RECT rc, HMENU hMenu)
  447. {
  448. return Create(hwndParent, pstrName, dwStyle, dwExStyle, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, hMenu);
  449. }
  450. HWND CWindowWnd::Create(HWND hwndParent, LPCTSTR pstrName, DWORD dwStyle, DWORD dwExStyle, int x, int y, int cx, int cy, HMENU hMenu)
  451. {
  452. if( GetSuperClassName() != NULL && !RegisterSuperclass() ) return NULL;
  453. if( GetSuperClassName() == NULL && !RegisterWindowClass() ) return NULL;
  454. if(m_bUnicode) {
  455. #ifndef UNICODE
  456. LPWSTR lpClassName = a2w((char*)GetWindowClassName());
  457. LPWSTR lpName = a2w((char*)pstrName);
  458. #else
  459. LPWSTR lpClassName = (LPWSTR)GetWindowClassName();
  460. LPWSTR lpName = (LPWSTR)pstrName;
  461. #endif
  462. m_hWnd = ::CreateWindowExW(dwExStyle, lpClassName, lpName, dwStyle, x, y, cx, cy, hwndParent, hMenu, CPaintManagerUI::GetInstance(), this);
  463. #ifndef UNICODE
  464. delete []lpClassName;
  465. delete []lpName;
  466. #endif
  467. }
  468. else {
  469. m_hWnd = ::CreateWindowEx(dwExStyle, GetWindowClassName(), pstrName, dwStyle, x, y, cx, cy, hwndParent, hMenu, CPaintManagerUI::GetInstance(), this);
  470. }
  471. ASSERT(m_hWnd!=NULL);
  472. RegisterTouchWindowIfAvailable(m_hWnd);
  473. return m_hWnd;
  474. }
  475. HWND CWindowWnd::Subclass(HWND hWnd)
  476. {
  477. ASSERT(::IsWindow(hWnd));
  478. ASSERT(m_hWnd==NULL);
  479. m_OldWndProc = SubclassWindow(hWnd, __WndProc);
  480. if( m_OldWndProc == NULL ) return NULL;
  481. m_bSubclassed = true;
  482. m_hWnd = hWnd;
  483. ::SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LPARAM>(this));
  484. RegisterTouchWindowIfAvailable(m_hWnd);
  485. return m_hWnd;
  486. }
  487. void CWindowWnd::Unsubclass()
  488. {
  489. ASSERT(::IsWindow(m_hWnd));
  490. if( !::IsWindow(m_hWnd) ) return;
  491. if( !m_bSubclassed ) return;
  492. SubclassWindow(m_hWnd, m_OldWndProc);
  493. m_OldWndProc = ::DefWindowProc;
  494. m_bSubclassed = false;
  495. }
  496. void CWindowWnd::ShowWindow(bool bShow /*= true*/, bool bTakeFocus /*= false*/)
  497. {
  498. ASSERT(::IsWindow(m_hWnd));
  499. if( !::IsWindow(m_hWnd) ) return;
  500. ::ShowWindow(m_hWnd, bShow ? (bTakeFocus ? SW_SHOWNORMAL : SW_SHOWNOACTIVATE) : SW_HIDE);
  501. }
  502. UINT CWindowWnd::ShowModal()
  503. {
  504. // =========================================================================
  505. // 【真模态】改用 Windows 的对话框机制(DialogBoxIndirectParam)驱动模态。
  506. //
  507. // 参考:网易 nim_duilib 的 NativeWindow_Windows::DoModal()
  508. // (duilib/Core/NativeWindow_Windows.cpp)
  509. //
  510. // ── 为什么必须换掉旧写法 ──────────────────────────────────────────────
  511. // 旧实现是"假模态":自己 ShowWindow + EnableWindow(owner,FALSE) + TOPMOST +
  512. // 抢前台 + 自写 while(GetMessage) 嵌套循环。在 Win10 触摸屏上必然失败,根因是
  513. // 触摸消息的投递目标规则(MSDN, Getting Started with Windows Touch Messages):
  514. //
  515. // "WM_TOUCH messages are currently 'greedy.' After the first touch message is
  516. // received on a window, all touch messages are sent to that window until
  517. // another window receives focus."
  518. //
  519. // 主窗口早就 RegisterTouchWindow 过、也早就收到过 WM_TOUCH,所以触摸归属权长期
  520. // 在它手里;弹框是自己 Create 出来的普通窗口,**不是**系统的触摸目标。
  521. // 我们为此打了一堆补丁(手动焦点切换、刷新注册、消息循环转发、__WndProc 转发兜底),
  522. // 实测全部无效:日志里弹框始终零 WM_TOUCH、零 WM_LBUTTONDOWN/UP。
  523. //
  524. // ── 对话框为什么能解决 ────────────────────────────────────────────────
  525. // DialogBox 是内核实现的模态机制,它:
  526. // · 由系统跑消息循环(不需要我们自写嵌套循环);
  527. // · 由系统按正确时机禁用 owner(不需要我们 EnableWindow);
  528. // · 由系统维持前台/激活(不需要 TOPMOST / SetForegroundWindow / 焦点切换);
  529. // · **把对话框窗口本身变成输入目标**,触摸自然投给它、提升的鼠标消息也落在它身上。
  530. //
  531. // 于是"贪婪归属权""提升鼠标被吞""owner 抢前台""禁用窗口收不到消息"这几类问题
  532. // 全部不存在 —— 不是被绕过,而是压根不成立。
  533. //
  534. // ── 如何与既有代码兼容 ────────────────────────────────────────────────
  535. // 调用方约定是:new → Create(hwnd, ..., UI_WNDSTYLE_DIALOG, ...) → SetIcon →
  536. // CenterWindow() → ShowModal()。也就是说**窗口在我们被调用之前就已经 Create 好了**,
  537. // CPaintManagerUI 也已 Init 到那个 HWND 上。所以这里不能另起一个对话框窗口,
  538. // 而是:用当前窗口的几何/样式造一个 DLGTEMPLATE,让 DialogBoxIndirectParam 去创建
  539. // 对话框窗口,再把旧的 HWND 销毁、把 DuiLib 的 manager 迁移到新 HWND —— 这样
  540. // 调用方完全无感知,`m_hWnd` 语义不变,30+ 个调用点一行都不用改。
  541. // =========================================================================
  542. ASSERT(::IsWindow(m_hWnd));
  543. if( !::IsWindow(m_hWnd) ) return 0;
  544. HWND hWndOwner = ::GetWindow(m_hWnd, GW_OWNER);
  545. if( hWndOwner == NULL ) hWndOwner = ::GetActiveWindow();
  546. // ---- 1) 采集旧窗口的几何与样式,用于构造对话框模板 ----
  547. RECT rcWnd = { 0 };
  548. ::GetWindowRect(m_hWnd, &rcWnd);
  549. const int nW = rcWnd.right - rcWnd.left;
  550. const int nH = rcWnd.bottom - rcWnd.top;
  551. // 【尺寸精确性】旧窗口的客户区尺寸 —— 这是迁移后必须保持不变的量。
  552. //
  553. // 为什么不能直接用窗口矩形(nW/nH)去填 DLGTEMPLATE.cx/cy:
  554. // 1. DLGTEMPLATE 的 cx/cy 描述的是**客户区**尺寸,不是窗口外框尺寸;
  555. // 2. 我们给模板设的样式(WS_CAPTION/WS_DLGFRAME/DS_MODALFRAME…)会让系统
  556. // 在此基础上再往外加一圈非客户区边框。
  557. // 若直接填 nW/nH,新窗口 = 旧窗口 + 边框 ⇒ 弹框整体变大、位置也偏。
  558. //
  559. // 因此这里取"旧窗口客户区尺寸"作为目标客户区尺寸;创建完成后再在 __DialogProc 里
  560. // 用 AdjustWindowRectEx 反算目标外框并 SetWindowPos 精确校正(见 __DialogProc)。
  561. RECT rcClient = { 0 };
  562. ::GetClientRect(m_hWnd, &rcClient);
  563. const int nClientW = rcClient.right - rcClient.left;
  564. const int nClientH = rcClient.bottom - rcClient.top;
  565. DWORD dwStyle = (DWORD)::GetWindowLong(m_hWnd, GWL_STYLE);
  566. DWORD dwExStyle = (DWORD)::GetWindowLong(m_hWnd, GWL_EXSTYLE);
  567. // 对话框专用的样式:把我们自己那套 WS_POPUPWINDOW/WS_CAPTION/WS_DLGFRAME 换成
  568. // 对话框该有的组合。WS_POPUP 必须保留(否则会变成带系统标题栏的普通窗口)。
  569. // WS_VISIBLE 让 DialogBox 创建后直接显示。
  570. //
  571. // ⚠️ 关于 WS_CAPTION / DS_MODALFRAME:
  572. // 本项目的弹框(UI_WNDSTYLE_DIALOG)是**无系统标题栏的自绘弹框**,且调用方
  573. // 均未传 WS_THICKFRAME(固定尺寸)。所以这里**不能**自作主张加 DS_MODALFRAME 或
  574. // WS_THICKFRAME —— 那会给弹框加一圈可调整大小的边框,既改变外观又改变尺寸。
  575. // 保持 WS_POPUP + 原有的 WS_CAPTION/WS_SYSMENU 即可(自绘弹框本来就不显示系统标题栏)。
  576. DWORD dwDlgStyle = WS_POPUP | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
  577. if( dwStyle & WS_CAPTION ) dwDlgStyle |= WS_CAPTION | WS_SYSMENU;
  578. if( dwStyle & WS_THICKFRAME ) dwDlgStyle |= WS_THICKFRAME;
  579. if( dwStyle & WS_MINIMIZEBOX ) dwDlgStyle |= WS_MINIMIZEBOX;
  580. if( dwStyle & WS_MAXIMIZEBOX ) dwDlgStyle |= WS_MAXIMIZEBOX;
  581. // ex-style 基本原样透传(WS_EX_TOOLWINDOW / WS_EX_WINDOWEDGE 等),但清掉两类:
  582. // · WS_EX_APPWINDOW —— 对话框不该出现在任务栏;
  583. // · WS_EX_DLGMODALFRAME —— 会给弹框加一圈双线边框,固定尺寸自绘弹框不需要(会改变外框尺寸)。
  584. dwExStyle &= ~(WS_EX_APPWINDOW | WS_EX_DLGMODALFRAME);
  585. // ---- 2) 构造 DLGTEMPLATE 资源 ----
  586. // 只用 DLGTEMPLATE 本体(不含菜单/类名/标题/字体),微软文档允许这样,
  587. // 系统会套用默认的对话框字体与类(#32770)。
  588. // 参考 nim_duilib:sizeof(DLGTEMPLATE) + 32 字节余量。
  589. const size_t nDataSize = sizeof(DLGTEMPLATE) + 32;
  590. HGLOBAL hResource = ::GlobalAlloc(GMEM_MOVEABLE, nDataSize);
  591. if( hResource == NULL )
  592. {
  593. DuiLib::TouchTrace::Log(_T("MODAL-DLG GlobalAlloc failed err=%u"), ::GetLastError());
  594. return 0;
  595. }
  596. LPDLGTEMPLATE pTemplate = (LPDLGTEMPLATE)::GlobalLock(hResource);
  597. if( pTemplate == NULL )
  598. {
  599. ::GlobalFree(hResource);
  600. return 0;
  601. }
  602. ::ZeroMemory(pTemplate, nDataSize);
  603. pTemplate->style = dwDlgStyle;
  604. pTemplate->dwExtendedStyle = dwExStyle;
  605. pTemplate->cdit = 0; // 不使用模板内控件(控件由 DuiLib 自绘)
  606. // DLGTEMPLATE 的 cx/cy 单位是对话框单位(DLU),且语义为**客户区**尺寸。
  607. // 这里只是给系统一个初始值(避免 0 尺寸导致奇怪的默认值),真正的精确尺寸
  608. // 由 __DialogProc 里创建完成后按旧客户区尺寸 + AdjustWindowRectEx 反算并 SetWindowPos 校正。
  609. pTemplate->x = 0;
  610. pTemplate->y = 0;
  611. pTemplate->cx = (short)(nClientW > 0 ? nClientW : nW);
  612. pTemplate->cy = (short)(nClientH > 0 ? nClientH : nH);
  613. // ---- 3) 迁移前先把旧的 HWND 记录下来,并让 manager 做好切换准备 ----
  614. HWND hWndOld = m_hWnd;
  615. m_bDoModal = true;
  616. m_nModalRet = 0;
  617. // 把"目标位置 + 目标客户区尺寸"暂存到成员,供 __DialogProc 在创建完成后精确校正。
  618. // 之所以不在模板里一次算准:对话框的非客户区厚度取决于实际样式(WS_CAPTION/WS_DLGFRAME/
  619. // WS_EX_* 组合),预估值容易差几个像素;创建后用 AdjustWindowRectEx 反算再 SetWindowPos
  620. // 是唯一能保证"客户区尺寸与旧窗口逐像素一致"的做法。
  621. m_rcModalTarget.left = rcWnd.left;
  622. m_rcModalTarget.top = rcWnd.top;
  623. m_rcModalTarget.right = rcWnd.left + nClientW;
  624. m_rcModalTarget.bottom = rcWnd.top + nClientH;
  625. DuiLib::TouchTrace::Log(_T("MODAL-DLG enter old=%p owner=%p wndrect=%d,%d,%dx%d client=%dx%d dlgstyle=0x%08X ex=0x%08X"),
  626. (void*)hWndOld, (void*)hWndOwner, rcWnd.left, rcWnd.top, nW, nH, nClientW, nClientH,
  627. (unsigned)dwDlgStyle, (unsigned)dwExStyle);
  628. // 记录"当前模态窗口",供库内其它地方判断(如 OnTouchInput 的老兜底逻辑)。
  629. // 注意:此时还不知道新 HWND,__DialogProc 里会再次 SetModalWindow。
  630. HWND hPrevModal = CPaintManagerUI::GetModalWindow();
  631. // 关键:先把旧窗口销毁、把 manager 解绑,再让 DialogBox 创建新的对话框窗口,
  632. // 这样 manager 不会出现"两个 HWND 同时活着"的错乱状态。
  633. // 说明:调用方是在 ShowModal() 之前 Create 的,所以这里必须主动清理旧窗口。
  634. HWND hWndOldCaptured = m_hWnd;
  635. m_hWnd = NULL; // 先置空:__DialogProc 会在 WM_INITDIALOG 里填入新句柄
  636. // 把 manager 从旧窗口上摘下来(保留它的控件树/资源,只换 paint window)
  637. CPaintManagerUI* pManager = FindManagerByPaintWindow(hWndOldCaptured);
  638. SetDetachedManager(pManager);
  639. if( pManager != NULL )
  640. {
  641. pManager->DetachPaintWindow();
  642. DuiLib::TouchTrace::Log(_T("MODAL-DLG detach manager=%p from old=%p"),
  643. (void*)pManager, (void*)hWndOldCaptured);
  644. }
  645. // 销毁旧窗口(旧窗口上的窗口过程会把 m_hWnd 清空,但我们已经在上面置 NULL 了,
  646. // 且 manager 已摘除,不会再触发 DuiLib 的绘制路径)。
  647. if( ::IsWindow(hWndOldCaptured) ) ::DestroyWindow(hWndOldCaptured);
  648. // ---- 4) 交给系统对话框管理器跑模态循环 ----
  649. // 这是阻塞调用,返回时模态已结束。
  650. INT_PTR nRet = ::DialogBoxIndirectParam(CPaintManagerUI::GetInstance(),
  651. pTemplate,
  652. hWndOwner,
  653. CWindowWnd::__DialogProc,
  654. (LPARAM)this);
  655. // 资源可以释放了(DialogBoxIndirectParam 内部已把模板复制走)——
  656. // 但谨慎起见放在 EndDialog 之后,与 nim_duilib 一致。
  657. ::GlobalUnlock(hResource);
  658. ::GlobalFree(hResource);
  659. // 兜底:若 __DialogProc 因故没跑(创建失败 / WM_INITDIALOG 里 early-return),
  660. // 这里把中转变量清干净,避免悬垂指针影响后续 ShowModal。
  661. SetDetachedManager(NULL);
  662. m_bDoModal = false;
  663. CPaintManagerUI::SetModalWindow(hPrevModal);
  664. DuiLib::TouchTrace::Log(_T("MODAL-DLG exit hwnd=%p ret=%d m_nModalRet=%u"),
  665. (void*)m_hWnd, (int)nRet, m_nModalRet);
  666. // DialogBoxIndirectParam 返回值:-1 表示创建失败
  667. if( nRet == -1 )
  668. {
  669. DuiLib::TouchTrace::Log(_T("MODAL-DLG failed err=%u"), ::GetLastError());
  670. return 0;
  671. }
  672. return m_nModalRet;
  673. }
  674. // -----------------------------------------------------------------------------
  675. // 真模态:对话框过程。
  676. //
  677. // 生命周期:
  678. // DialogBoxIndirectParam → 创建 #32770 窗口 → WM_INITDIALOG(此时才有真 HWND)
  679. // → 把 manager 挂到新 HWND、换掉 GWLP_WNDPROC
  680. // → return TRUE(表示我们已经处理了焦点)
  681. // -----------------------------------------------------------------------------
  682. INT_PTR CALLBACK CWindowWnd::__DialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
  683. {
  684. if( uMsg == WM_INITDIALOG )
  685. {
  686. // 这是对话框的第一个消息;lParam 是我们传进来的 this
  687. CWindowWnd* pThis = reinterpret_cast<CWindowWnd*>(lParam);
  688. if( pThis == NULL ) return TRUE;
  689. ASSERT(pThis->m_hWnd == NULL);
  690. pThis->m_hWnd = hDlg;
  691. // ---- 换掉对话框默认的窗口过程 ----
  692. // 这样后续所有消息(含 WM_PAINT / WM_TOUCH / 鼠标)都会走到
  693. // CWindowWnd::HandleMessage → CPaintManagerUI,DuiLib 的自绘与路由完全保留。
  694. ASSERT(pThis->m_pfnOldDlgWndProc == NULL);
  695. pThis->m_pfnOldDlgWndProc = (WNDPROC)::GetWindowLongPtr(hDlg, GWLP_WNDPROC);
  696. ::SetWindowLongPtr(hDlg, GWLP_WNDPROC, (LONG_PTR)(WNDPROC)CWindowWnd::__DialogWndProc);
  697. ::SetWindowLongPtr(hDlg, GWLP_USERDATA, (LONG_PTR)pThis);
  698. // ---- 登记为当前模态窗口 ----
  699. CPaintManagerUI::SetModalWindow(hDlg);
  700. // ---- 触摸注册:对话框是全新的 HWND,必须注册,否则收不到 WM_TOUCH ----
  701. RegisterTouchWindowIfAvailable(hDlg);
  702. DuiLib::TouchTrace::Log(_T("MODAL-DLG init dlg=%p oldwndproc=%p focus=%p"),
  703. (void*)hDlg, (void*)pThis->m_pfnOldDlgWndProc, (void*)::GetFocus());
  704. // ---- 把旧的 CPaintManagerUI 挂到对话框窗口上 ----
  705. // 调用方在 ShowModal() 之前已 Create + manager.Init 到旧 HWND;这里把 manager 迁移过来,
  706. // 让 DuiLib 的全部既有逻辑(绘制 / 消息 / 控件树)在新窗口上继续工作。
  707. CPaintManagerUI* pManager = GetDetachedManager();
  708. if( pManager != NULL )
  709. {
  710. pManager->AttachPaintWindow(hDlg);
  711. DuiLib::TouchTrace::Log(_T("MODAL-DLG attach manager=%p to dlg=%p"), (void*)pManager, (void*)hDlg);
  712. // 用掉即清:避免这个文件级静态变量残留一个已失效的指针,
  713. // 影响下一次 ShowModal(尤其嵌套弹框:A 弹框里再开 B 弹框)。
  714. SetDetachedManager(NULL);
  715. }
  716. else
  717. {
  718. DuiLib::TouchTrace::Log(_T("MODAL-DLG no detached manager (dialog will Init itself)"));
  719. }
  720. // ---- [WM_TOUCH 归属权] 立刻把"补调原窗口过程"的钩子绑到本弹框 ----
  721. // 为什么必须在这里(而不是等 __DialogWndProc 首次进入时再绑):
  722. // __DialogProc 在 WM_INITDIALOG 里直接调 pThis->HandleMessage(见下方),
  723. // 这条路径**不经过** __DialogWndProc,所以"首次进入窗口过程时绑定"
  724. // 在这条路上不会触发。而 AttachPaintWindow 之后 manager 已经指向 hDlg,
  725. // 一旦有 WM_MOUSEMOVE 到达就可能需要补调 ⇒ 必须现在就绑好。
  726. // 绑定对象是 m_pfnOldDlgWndProc(即 #32770 原过程 / DefDlgProc)。
  727. if( pManager != NULL )
  728. {
  729. // m_pfnOldDlgWndProc 本身就是 WNDPROC;只有它意外为空时才回落到 DefDlgProc
  730. // (签名是 INT_PTR 返回,需显式转换,不能与 WNDPROC 混在三目里)。
  731. WNDPROC pfnDlgTarget = pThis->m_pfnOldDlgWndProc;
  732. if( pfnDlgTarget == NULL )
  733. pfnDlgTarget = reinterpret_cast<WNDPROC>(::DefDlgProc);
  734. BindRelayTarget(pfnDlgTarget, hDlg);
  735. pManager->SetDefProcRelay(&RelayWndProc, hDlg);
  736. }
  737. // ---- 尺寸校正(关键)----
  738. // DLGTEMPLATE 里给的 cx/cy 是对话框单位(DLU)、且语义为「客户区」,系统创建时还会
  739. // 按实际样式额外加一圈非客户区边框。所以不能指望"模板尺寸 == 目标尺寸"。
  740. // 这里用 AdjustWindowRectEx 按对话框**真实**的样式反算目标外框,再 SetWindowPos 精确校正,
  741. // 保证客户区尺寸与原窗口逐像素一致 —— 这正是"弹框变大了"的修法。
  742. //
  743. // 放在 AttachPaintWindow 之后、HandleMessage(WM_INITDIALOG) 之前:
  744. // 此时 manager 已绑定新 HWND,尺寸变化会走正常的布局流程;且业务层 OnCreate 之前
  745. // 窗口就已经是最终尺寸,避免"先小后大"的闪动与一次性布局错位。
  746. {
  747. const DWORD dwRealStyle = (DWORD)::GetWindowLong(hDlg, GWL_STYLE);
  748. const DWORD dwRealExStyle = (DWORD)::GetWindowLong(hDlg, GWL_EXSTYLE);
  749. RECT rcAdjust = { 0, 0,
  750. pThis->m_rcModalTarget.right - pThis->m_rcModalTarget.left,
  751. pThis->m_rcModalTarget.bottom - pThis->m_rcModalTarget.top };
  752. const int nClientW = rcAdjust.right;
  753. const int nClientH = rcAdjust.bottom;
  754. // 反算:以"目标客户区尺寸"为输入,得到需要的外框尺寸
  755. ::AdjustWindowRectEx(&rcAdjust, dwRealStyle, FALSE, dwRealExStyle);
  756. const int nWndW = rcAdjust.right - rcAdjust.left;
  757. const int nWndH = rcAdjust.bottom - rcAdjust.top;
  758. RECT rcCur = { 0 };
  759. ::GetWindowRect(hDlg, &rcCur);
  760. DuiLib::TouchTrace::Log(_T("MODAL-DLG resize before cur=%dx%d clientTarget=%dx%d -> wnd=%dx%d hide=%d"),
  761. rcCur.right - rcCur.left, rcCur.bottom - rcCur.top,
  762. nClientW, nClientH, nWndW, nWndH,
  763. (int)((dwRealStyle & WS_VISIBLE) == 0));
  764. // SWP_FRAMECHANGED 让系统重新计算非客户区(样式刚被 SetWindowLongPtr 换过)
  765. ::SetWindowPos(hDlg, NULL,
  766. pThis->m_rcModalTarget.left, pThis->m_rcModalTarget.top,
  767. nWndW, nWndH,
  768. SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
  769. RECT rcAfter = { 0 };
  770. ::GetClientRect(hDlg, &rcAfter);
  771. DuiLib::TouchTrace::Log(_T("MODAL-DLG resize after client=%dx%d (target=%dx%d)"),
  772. rcAfter.right - rcAfter.left, rcAfter.bottom - rcAfter.top, nClientW, nClientH);
  773. }
  774. // 交给 CWindowWnd::HandleMessage 处理 WM_INITDIALOG,业务层的 OnCreate 等由此触发。
  775. pThis->HandleMessage(uMsg, wParam, lParam);
  776. ::SetFocus(hDlg);
  777. return TRUE; // TRUE = 我们自己设置了焦点
  778. }
  779. return FALSE;
  780. }
  781. // -----------------------------------------------------------------------------
  782. // 真模态:对话框窗口的窗口过程。
  783. // 几乎就是 __WndProc 的对话框版本。
  784. // -----------------------------------------------------------------------------
  785. LRESULT CALLBACK CWindowWnd::__DialogWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
  786. {
  787. CWindowWnd* pThis = reinterpret_cast<CWindowWnd*>(::GetWindowLongPtr(hWnd, GWLP_USERDATA));
  788. if( uMsg == WM_NCDESTROY && pThis != NULL )
  789. {
  790. // 顺序很关键:
  791. // 1) 先把窗口过程还原(此后系统调用会回到对话框默认过程);
  792. // 2) 再让 DefWindowProc 做标准清理;
  793. // 3) 清 GWLP_USERDATA —— 必须在 HandleMessage 之前,防止重入时又拿到 this;
  794. // 4) 清模态登记;
  795. // 5) m_hWnd 置空【要在 Unsubclass 之前】—— 注意本项目弹框并不 Subclass,
  796. // m_bSubclassed 恒为 false,这里只是保持与 __WndProc 一致的写法,
  797. // 并让 OnFinalMessage 里看到的 m_hWnd 为空(与旧行为一致)。
  798. if( pThis->m_pfnOldDlgWndProc != NULL )
  799. {
  800. ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)pThis->m_pfnOldDlgWndProc);
  801. pThis->m_pfnOldDlgWndProc = NULL;
  802. }
  803. LRESULT lRes = ::DefWindowProc(hWnd, uMsg, wParam, lParam);
  804. // [WM_TOUCH 归属权] 解绑转发目标,避免悬垂到已销毁的对话框窗口过程。
  805. {
  806. DuiLib::CPaintManagerUI* pMgrNc = FindManagerByPaintWindow(hWnd);
  807. if( pMgrNc != NULL ) pMgrNc->ClearDefProcRelay(hWnd);
  808. BindRelayTarget(NULL, NULL);
  809. }
  810. ::SetWindowLongPtr(hWnd, GWLP_USERDATA, 0L);
  811. CPaintManagerUI::SetModalWindow(NULL);
  812. pThis->m_hWnd = NULL;
  813. if( pThis->m_bSubclassed ) pThis->Unsubclass();
  814. pThis->OnFinalMessage(hWnd);
  815. return lRes;
  816. }
  817. // WM_INITDIALOG 已经由 __DialogProc 处理并 return TRUE,
  818. // 一般不会再走到这里;若系统仍投递(例如 EndDialog 之后的收尾路径),
  819. // 直接交回对话框默认过程,避免被 HandleMessage 当成普通消息误处理。
  820. if( uMsg == WM_INITDIALOG )
  821. return ::DefDlgProc(hWnd, uMsg, wParam, lParam);
  822. // 诊断:确认 WM_TOUCH 到底投递给了哪个窗口。
  823. if( uMsg == WM_TOUCH ) TraceTouchArrival(hWnd, wParam, lParam);
  824. if( pThis != NULL ) TraceInputMessage(hWnd, uMsg, wParam, lParam);
  825. if( pThis != NULL )
  826. {
  827. // ---------------------------------------------------------------
  828. // [WM_TOUCH 归属权] 注册"补调原窗口过程"的钩子。
  829. //
  830. // 真模态弹框的窗口类是系统对话框类 #32770,它把"把特殊标记的
  831. // WM_MOUSEMOVE 翻译成 WM_TOUCH"这件事放在 DefDlgProc → DefWindowProc 里做。
  832. // 而 __DialogWndProc 一旦 pThis != NULL 就全部交给 HandleMessage,
  833. // 再也不回落 DefDlgProc ⇒ 弹框永远收不到 WM_TOUCH(与实测吻合)。
  834. //
  835. // 这里登记的原过程是 m_pfnOldDlgWndProc(也就是 DefDlgProc),
  836. // 使 manager 在吞掉触摸提升鼠标消息时能补调它一次,把翻译链接上。
  837. // ---------------------------------------------------------------
  838. // ⚠️ 判据必须是"当前绑定的窗口不是本窗口",不能写 `GetDefProcRelay() == NULL`:
  839. // 真模态弹框复用的是**同一个** manager 对象(从旧窗口 Detach、再 Attach 到弹框),
  840. // 主窗口早已注册过钩子 ⇒ 按 NULL 判断则弹框永远不重新绑定,
  841. // 转发目标会一直指向主窗口的原过程,补调无效甚至喂错窗口。
  842. DuiLib::CPaintManagerUI* pMgr = FindManagerByPaintWindow(hWnd);
  843. if( pMgr != NULL && pMgr->GetDefProcRelayBoundWindow() != hWnd )
  844. {
  845. // ⚠️ DefDlgProc 的签名是 `INT_PTR (CALLBACK*)(...)`,与 WNDPROC
  846. // (`LRESULT (CALLBACK*)(...)`)返回类型不同,三目运算符会类型不匹配。
  847. // 用一个显式分支赋值,避免 C2446。
  848. WNDPROC pfnTarget = NULL;
  849. if( pThis->m_pfnOldDlgWndProc != NULL )
  850. pfnTarget = pThis->m_pfnOldDlgWndProc;
  851. else
  852. pfnTarget = reinterpret_cast<WNDPROC>(::DefDlgProc);
  853. BindRelayTarget(pfnTarget, hWnd);
  854. pMgr->SetDefProcRelay(&RelayWndProc, hWnd);
  855. }
  856. return pThis->HandleMessage(uMsg, wParam, lParam);
  857. }
  858. return ::DefDlgProc(hWnd, uMsg, wParam, lParam);
  859. }
  860. void CWindowWnd::ShowModalFake()
  861. {
  862. ASSERT(::IsWindow(m_hWnd));
  863. auto p_hwnd = GetWindowOwner(m_hWnd);
  864. ASSERT(::IsWindow(p_hwnd));
  865. ASSERT(p_hwnd);
  866. ::EnableWindow(p_hwnd, FALSE);
  867. ShowWindow();
  868. m_bFakeModal = true;
  869. }
  870. void CWindowWnd::Close(UINT nRet)
  871. {
  872. if (m_bFakeModal)
  873. {
  874. auto parent_hwnd = GetWindowOwner(m_hWnd);
  875. ASSERT(::IsWindow(parent_hwnd));
  876. ::EnableWindow(parent_hwnd, TRUE);
  877. ::SetFocus(parent_hwnd);
  878. m_bFakeModal = false;
  879. }
  880. ASSERT(::IsWindow(m_hWnd));
  881. if (!::IsWindow(m_hWnd)) return;
  882. // -------------------------------------------------------------------------
  883. // 真模态:走 EndDialog 结束系统对话框循环。
  884. //
  885. // 旧的假模态是 PostMessage(WM_CLOSE) 让自写循环退出;真模态下必须用 EndDialog,
  886. // 否则 DialogBoxIndirectParam 不会返回。返回值写入 m_nModalRet,由 ShowModal 读走。
  887. //
  888. // 注意:EndDialog 会销毁对话框窗口 → 触发 WM_NCDESTROY → __DialogWndProc 收尾
  889. // (还原窗口过程、清 USERDATA、调 OnFinalMessage)。所以这里不要再 DestroyWindow。
  890. // -------------------------------------------------------------------------
  891. if (m_bDoModal)
  892. {
  893. m_nModalRet = nRet;
  894. DuiLib::TouchTrace::Log(_T("MODAL-DLG EndDialog hwnd=%p ret=%u"), (void*)m_hWnd, nRet);
  895. ::EndDialog(m_hWnd, (INT_PTR)nRet);
  896. return;
  897. }
  898. PostMessage(WM_CLOSE, (WPARAM)nRet, 0L);
  899. }
  900. void CWindowWnd::CenterWindow()
  901. {
  902. ASSERT(::IsWindow(m_hWnd));
  903. ASSERT((GetWindowStyle(m_hWnd)&WS_CHILD)==0);
  904. RECT rcDlg = { 0 };
  905. ::GetWindowRect(m_hWnd, &rcDlg);
  906. RECT rcArea = { 0 };
  907. RECT rcCenter = { 0 };
  908. HWND hWnd=*this;
  909. HWND hWndParent = ::GetParent(m_hWnd);
  910. HWND hWndCenter = ::GetWindowOwner(m_hWnd);
  911. if (hWndCenter!=NULL)
  912. hWnd=hWndCenter;
  913. // 处理多显示器模式下屏幕居中
  914. MONITORINFO oMonitor = {};
  915. oMonitor.cbSize = sizeof(oMonitor);
  916. ::GetMonitorInfo(::MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST), &oMonitor);
  917. rcArea = oMonitor.rcWork;
  918. if( hWndCenter == NULL )
  919. rcCenter = rcArea;
  920. else
  921. ::GetWindowRect(hWndCenter, &rcCenter);
  922. int DlgWidth = rcDlg.right - rcDlg.left;
  923. int DlgHeight = rcDlg.bottom - rcDlg.top;
  924. // Find dialog's upper left based on rcCenter
  925. int xLeft = (rcCenter.left + rcCenter.right) / 2 - DlgWidth / 2;
  926. int yTop = (rcCenter.top + rcCenter.bottom) / 2 - DlgHeight / 2;
  927. // The dialog is outside the screen, move it inside
  928. if( xLeft < rcArea.left ) xLeft = rcArea.left;
  929. else if( xLeft + DlgWidth > rcArea.right ) xLeft = rcArea.right - DlgWidth;
  930. if( yTop < rcArea.top ) yTop = rcArea.top;
  931. else if( yTop + DlgHeight > rcArea.bottom ) yTop = rcArea.bottom - DlgHeight;
  932. ::SetWindowPos(m_hWnd, NULL, xLeft, yTop, -1, -1, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
  933. }
  934. void CWindowWnd::SetIcon(UINT nRes)
  935. {
  936. HICON hIcon = (HICON)::LoadImage(CPaintManagerUI::GetInstance(), MAKEINTRESOURCE(nRes), IMAGE_ICON,
  937. (::GetSystemMetrics(SM_CXICON) + 15) & ~15, (::GetSystemMetrics(SM_CYICON) + 15) & ~15, // 防止高DPI下图标模糊
  938. LR_DEFAULTCOLOR);
  939. ASSERT(hIcon);
  940. ::SendMessage(m_hWnd, WM_SETICON, (WPARAM) TRUE, (LPARAM) hIcon);
  941. hIcon = (HICON)::LoadImage(CPaintManagerUI::GetInstance(), MAKEINTRESOURCE(nRes), IMAGE_ICON,
  942. (::GetSystemMetrics(SM_CXICON) + 15) & ~15, (::GetSystemMetrics(SM_CYICON) + 15) & ~15, // 防止高DPI下图标模糊
  943. LR_DEFAULTCOLOR);
  944. ASSERT(hIcon);
  945. ::SendMessage(m_hWnd, WM_SETICON, (WPARAM) FALSE, (LPARAM) hIcon);
  946. }
  947. bool CWindowWnd::RegisterWindowClass()
  948. {
  949. WNDCLASS wc = { 0 };
  950. wc.style = GetClassStyle();
  951. wc.cbClsExtra = 0;
  952. wc.cbWndExtra = 0;
  953. wc.hIcon = NULL;
  954. wc.lpfnWndProc = CWindowWnd::__WndProc;
  955. wc.hInstance = CPaintManagerUI::GetInstance();
  956. wc.hCursor = ::LoadCursor(NULL, IDC_ARROW);
  957. wc.hbrBackground = NULL;
  958. wc.lpszMenuName = NULL;
  959. wc.lpszClassName = GetWindowClassName();
  960. ATOM ret = ::RegisterClass(&wc);
  961. ASSERT(ret!=NULL || ::GetLastError()==ERROR_CLASS_ALREADY_EXISTS);
  962. return ret != NULL || ::GetLastError() == ERROR_CLASS_ALREADY_EXISTS;
  963. }
  964. bool CWindowWnd::RegisterSuperclass()
  965. {
  966. if(m_bUnicode) {
  967. #ifndef UNICODE
  968. LPWSTR lpSuperClassName = a2w((char*)GetSuperClassName());
  969. LPWSTR lpClassName = a2w((char*)GetWindowClassName());
  970. #else
  971. LPWSTR lpSuperClassName = (LPWSTR)GetSuperClassName();
  972. LPWSTR lpClassName = (LPWSTR)GetWindowClassName();
  973. #endif
  974. // Get the class information from an existing
  975. // window so we can subclass it later on...
  976. WNDCLASSEXW wc = { 0 };
  977. wc.cbSize = sizeof(WNDCLASSEXW);
  978. if( !::GetClassInfoExW(NULL, lpSuperClassName, &wc) ) {
  979. if( !::GetClassInfoExW(CPaintManagerUI::GetInstance(), lpSuperClassName, &wc) ) {
  980. ASSERT(!"Unable to locate window class");
  981. return NULL;
  982. }
  983. }
  984. m_OldWndProc = wc.lpfnWndProc;
  985. wc.lpfnWndProc = CWindowWnd::__ControlProc;
  986. wc.hInstance = CPaintManagerUI::GetInstance();
  987. wc.lpszClassName = lpClassName;
  988. ATOM ret = ::RegisterClassExW(&wc);
  989. #ifndef UNICODE
  990. delete []lpClassName;
  991. delete []lpSuperClassName;
  992. #endif
  993. ASSERT(ret!=NULL || ::GetLastError()==ERROR_CLASS_ALREADY_EXISTS);
  994. return ret != NULL || ::GetLastError() == ERROR_CLASS_ALREADY_EXISTS;
  995. }
  996. else {
  997. // Get the class information from an existing
  998. // window so we can subclass it later on...
  999. WNDCLASSEX wc = { 0 };
  1000. wc.cbSize = sizeof(WNDCLASSEX);
  1001. if( !::GetClassInfoEx(NULL, GetSuperClassName(), &wc) ) {
  1002. if( !::GetClassInfoEx(CPaintManagerUI::GetInstance(), GetSuperClassName(), &wc) ) {
  1003. ASSERT(!"Unable to locate window class");
  1004. return NULL;
  1005. }
  1006. }
  1007. m_OldWndProc = wc.lpfnWndProc;
  1008. wc.lpfnWndProc = CWindowWnd::__ControlProc;
  1009. wc.hInstance = CPaintManagerUI::GetInstance();
  1010. wc.lpszClassName = GetWindowClassName();
  1011. ATOM ret = ::RegisterClassEx(&wc);
  1012. ASSERT(ret!=NULL || ::GetLastError()==ERROR_CLASS_ALREADY_EXISTS);
  1013. return ret != NULL || ::GetLastError() == ERROR_CLASS_ALREADY_EXISTS;
  1014. }
  1015. }
  1016. // 注:RelayWndProc / BindRelayTarget 已上移到文件前部(FindManagerByPaintWindow 之后),
  1017. // 因为 __DialogWndProc 比本函数先出现、也要用到它们。
  1018. LRESULT CALLBACK CWindowWnd::__WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
  1019. { CWindowWnd* pThis = NULL;
  1020. if( uMsg == WM_NCCREATE ) {
  1021. LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam);
  1022. pThis = static_cast<CWindowWnd*>(lpcs->lpCreateParams);
  1023. pThis->m_hWnd = hWnd;
  1024. ::SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LPARAM>(pThis));
  1025. }
  1026. else {
  1027. pThis = reinterpret_cast<CWindowWnd*>(::GetWindowLongPtr(hWnd, GWLP_USERDATA));
  1028. if( uMsg == WM_NCDESTROY && pThis != NULL ) {
  1029. LRESULT lRes = ::CallWindowProc(pThis->m_OldWndProc, hWnd, uMsg, wParam, lParam);
  1030. // [WM_TOUCH 归属权] 解绑转发目标,避免悬垂到已销毁窗口的窗口过程。
  1031. DuiLib::CPaintManagerUI* pMgrNc = FindManagerByPaintWindow(hWnd);
  1032. if( pMgrNc != NULL ) pMgrNc->ClearDefProcRelay(hWnd);
  1033. BindRelayTarget(NULL, NULL);
  1034. ::SetWindowLongPtr(pThis->m_hWnd, GWLP_USERDATA, 0L);
  1035. if( pThis->m_bSubclassed ) pThis->Unsubclass();
  1036. pThis->m_hWnd = NULL;
  1037. pThis->OnFinalMessage(hWnd);
  1038. return lRes;
  1039. }
  1040. }
  1041. // 诊断:确认 WM_TOUCH 到底投递给了哪个窗口(弹框 / owner / 其它)。
  1042. if( uMsg == WM_TOUCH ) TraceTouchArrival(hWnd, wParam, lParam);
  1043. // 【临时全量诊断】所有输入类消息(含 WM_TOUCH / WM_GESTURE / WM_POINTER* / 鼠标 / 激活)
  1044. // 都记一条,用来判断系统到底把消息发给了谁。
  1045. if( pThis != NULL ) TraceInputMessage(hWnd, uMsg, wParam, lParam);
  1046. if( pThis != NULL ) {
  1047. // ---------------------------------------------------------------
  1048. // [WM_TOUCH 归属权] 注册"补调原窗口过程"的钩子。
  1049. // 把 m_OldWndProc(本窗口类的原过程)登记为转发目标,并交给 manager。
  1050. // manager 在吞掉"触摸提升 WM_MOUSEMOVE"时会回调它一次,
  1051. // 让系统的 WM_TOUCH 翻译链不断掉。
  1052. //
  1053. // 判据用"绑定窗口 != 本窗口"而非"钩子为空":真模态弹框复用同一个
  1054. // manager,换 HWND 时必须能重新绑定到对话框架自己的原过程。
  1055. // 开销:仅在窗口首个消息时走一遍查找,之后靠 manager 内的指针直连。
  1056. // ---------------------------------------------------------------
  1057. DuiLib::CPaintManagerUI* pMgr = FindManagerByPaintWindow(hWnd);
  1058. if( pMgr != NULL && pMgr->GetDefProcRelayBoundWindow() != hWnd )
  1059. {
  1060. BindRelayTarget(pThis->m_OldWndProc, hWnd);
  1061. pMgr->SetDefProcRelay(&RelayWndProc, hWnd);
  1062. }
  1063. return pThis->HandleMessage(uMsg, wParam, lParam);
  1064. }
  1065. else {
  1066. return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
  1067. }
  1068. }
  1069. LRESULT CALLBACK CWindowWnd::__ControlProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
  1070. {
  1071. CWindowWnd* pThis = NULL;
  1072. if( uMsg == WM_NCCREATE ) {
  1073. LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam);
  1074. pThis = static_cast<CWindowWnd*>(lpcs->lpCreateParams);
  1075. ::SetProp(hWnd, _T("WndX"), (HANDLE) pThis);
  1076. pThis->m_hWnd = hWnd;
  1077. }
  1078. else {
  1079. pThis = reinterpret_cast<CWindowWnd*>(::GetProp(hWnd, _T("WndX")));
  1080. if( uMsg == WM_NCDESTROY && pThis != NULL ) {
  1081. LRESULT lRes = ::CallWindowProc(pThis->m_OldWndProc, hWnd, uMsg, wParam, lParam);
  1082. if( pThis->m_bSubclassed ) pThis->Unsubclass();
  1083. ::SetProp(hWnd, _T("WndX"), NULL);
  1084. pThis->m_hWnd = NULL;
  1085. pThis->OnFinalMessage(hWnd);
  1086. return lRes;
  1087. }
  1088. }
  1089. if( pThis != NULL ) {
  1090. return pThis->HandleMessage(uMsg, wParam, lParam);
  1091. }
  1092. else {
  1093. return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
  1094. }
  1095. }
  1096. LRESULT CWindowWnd::SendMessage(UINT uMsg, WPARAM wParam /*= 0*/, LPARAM lParam /*= 0*/)
  1097. {
  1098. ASSERT(::IsWindow(m_hWnd));
  1099. return ::SendMessage(m_hWnd, uMsg, wParam, lParam);
  1100. }
  1101. LRESULT CWindowWnd::PostMessage(UINT uMsg, WPARAM wParam /*= 0*/, LPARAM lParam /*= 0*/)
  1102. {
  1103. ASSERT(::IsWindow(m_hWnd));
  1104. return ::PostMessage(m_hWnd, uMsg, wParam, lParam);
  1105. }
  1106. void CWindowWnd::ResizeClient(int cx /*= -1*/, int cy /*= -1*/)
  1107. {
  1108. ASSERT(::IsWindow(m_hWnd));
  1109. RECT rc = { 0 };
  1110. if( !::GetClientRect(m_hWnd, &rc) ) return;
  1111. if( cx != -1 ) rc.right = cx;
  1112. if( cy != -1 ) rc.bottom = cy;
  1113. if( !::AdjustWindowRectEx(&rc, GetWindowStyle(m_hWnd), (!(GetWindowStyle(m_hWnd) & WS_CHILD) && (::GetMenu(m_hWnd) != NULL)), GetWindowExStyle(m_hWnd)) ) return;
  1114. ::SetWindowPos(m_hWnd, NULL, 0, 0, rc.right - rc.left, rc.bottom - rc.top, SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE);
  1115. }
  1116. LRESULT CWindowWnd::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
  1117. {
  1118. return ::CallWindowProc(m_OldWndProc, m_hWnd, uMsg, wParam, lParam);
  1119. }
  1120. void CWindowWnd::OnFinalMessage(HWND /*hWnd*/)
  1121. {
  1122. }
  1123. } // namespace DuiLib