UIBase.cpp 65 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408
  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. namespace {
  15. // -------------------------------------------------------------------------
  16. // [触摸重复处理] 一次触摸手势会被处理两遍,第 2 遍必须作废。
  17. //
  18. // 第 1 遍:DuiLib 的触摸状态机(WM_TOUCH / WM_POINTER*)合成点击并派发业务回调;
  19. // 第 2 遍:Windows 把同一次触摸"提升"成的鼠标消息(extra 带 MOUSEEVENTF_FROMTOUCH,
  20. // 即 GetMessageExtraInfo() 高位 0xFF5157xx),排在消息队列里稍后投出。
  21. //
  22. // 第 2 遍是纯重复,造成的两类症状:
  23. // · Win7 点控件→回调里 ShowModal→"咚"(系统对"点击被禁用窗口"的默认处理 MessageBeep);
  24. // · 点弹框按钮后透传到背后主窗口控件。
  25. //
  26. // 处理分两处:
  27. // ① 投递之前让它不存在 —— CWindowWnd::ShowModal 进模态循环前用 PeekMessage 摘掉
  28. // 在途的 owner 鼠标副本(详见那里的注释)。这是"咚"的根治点。
  29. // ② 漏网的靠 CPaintManagerUI 里的 PROMOTED-DUP 闸门拦(见 UIManager.cpp)。
  30. //
  31. // 用户明确划定的边界(改动时务必守住):
  32. // · 点主窗口控件→回调弹框,那一声"咚"必须消除;
  33. // · 弹框**已经出现之后**点弹框外空白区的"咚"属正常反馈,**不要改**。
  34. // -------------------------------------------------------------------------
  35. typedef BOOL (WINAPI* PFN_REGISTER_TOUCH_WINDOW)(HWND, ULONG);
  36. void RegisterTouchWindowIfAvailable(HWND hWnd)
  37. {
  38. if (hWnd == NULL) return;
  39. HMODULE hUser32 = ::GetModuleHandle(_T("user32.dll"));
  40. if (hUser32 == NULL) return;
  41. PFN_REGISTER_TOUCH_WINDOW pfnRegister =
  42. reinterpret_cast<PFN_REGISTER_TOUCH_WINDOW>(::GetProcAddress(hUser32, "RegisterTouchWindow"));
  43. if (pfnRegister != NULL)
  44. pfnRegister(hWnd, 0);
  45. }
  46. // 【已搬移】FindManagerByPaintWindow 原本定义在这里(匿名 namespace 内)。
  47. //
  48. // 为什么必须搬走:UIBase.h 里为了让 UIManager.cpp 也能调用它,已经加了一份
  49. // 声明 `DuiLib::CPaintManagerUI* FindManagerByPaintWindow(HWND);`。
  50. // 于是同一个翻译单元里同时存在
  51. // · 匿名 namespace 内的 DuiLib::`anonymous-namespace'::FindManagerByPaintWindow
  52. // · 头文件声明的 DuiLib::FindManagerByPaintWindow
  53. // 两个候选,调用点(UIBase.cpp 里 793/1076/1124/1207/1367/1395 等)无法抉择,
  54. // MSVC 直接报 C2668「对重载函数的调用不明确」。
  55. // ⇒ 现在把它挪到匿名 namespace **之外**,作为 DuiLib 的正式成员,
  56. // 与头文件那份声明严格一一对应(定义见本文件下方 namespace 收尾之后)。
  57. // ---------------------------------------------------------------------------
  58. // [WM_TOUCH 归属权] "补调原窗口过程"的中转函数。
  59. //
  60. // 为什么需要它:
  61. // WM_TOUCH 不是被系统直接投递的。系统先发一条"特殊标记的 WM_MOUSEMOVE"
  62. // (开了 raw input 时是 WM_INPUT),只有这条消息走到 DefWindowProc 里,
  63. // DefWindowProc 才会把驻留的 WM_TOUCH "翻译"并派发出来
  64. // (来源:The Witness 作者的逆向结论;MSDN《Architectural Overview》的
  65. // greedy 段落也印证了这套投递模型)。
  66. // 本库的 CPaintManagerUI::MessageHandler 为了不让"触摸提升出来的鼠标移动"
  67. // 破坏拖拽/悬浮状态机,会吞掉它(return true),从而跳过了 DefWindowProc
  68. // ⇒ 窗口(尤其 #32770 真模态弹框,它依赖 DefDlgProc→DefWindowProc 这条链)
  69. // 后续永远收不到 WM_TOUCH。
  70. //
  71. // 这个中转函数由 CPaintManagerUI::RelayDefWindowProc 回调,
  72. // 作用就是"把消息喂给原窗口过程一次",补上被跳过的那次副作用。
  73. // 用静态变量保存"当前转发目标":因为回调签名不带 this,
  74. // 而每个窗口过程在同一时刻只有一个绑定关系(主窗口 or 某个弹框)。
  75. // 真模态切换 HWND 时通过 SetDefProcRelay 重新绑定,不会串。
  76. //
  77. // ⚠️ 位置要求:必须定义在 __DialogWndProc 之前(它比 __WndProc 先出现)。
  78. // ---------------------------------------------------------------------------
  79. WNDPROC g_pfnRelayTarget = NULL;
  80. HWND g_hWndRelayBound = NULL;
  81. LRESULT CALLBACK RelayWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
  82. {
  83. // 只有"当前正在转发的窗口"才有效:避免悬垂到已销毁窗口的过程上。
  84. if (g_pfnRelayTarget == NULL || g_hWndRelayBound != hWnd)
  85. return 0;
  86. return ::CallWindowProc(g_pfnRelayTarget, hWnd, uMsg, wParam, lParam);
  87. }
  88. // 把"原窗口过程"登记为转发目标(绑定到指定 HWND)。
  89. void BindRelayTarget(WNDPROC pfnOldProc, HWND hWnd)
  90. {
  91. g_pfnRelayTarget = pfnOldProc;
  92. g_hWndRelayBound = hWnd;
  93. }
  94. // DetachPaintWindow 之后、AttachPaintWindow 之前,用这个取出"待挂载的 manager"。
  95. // 因为此时 manager 已从 m_aPreMessages 摘除,FindManagerByPaintWindow 找不到它了,
  96. // 所以用一个文件级静态变量中转(只在 ShowModal 的单线程窗口过程里使用,无需同步)。
  97. static DuiLib::CPaintManagerUI* g_pDetachedManager = NULL;
  98. void SetDetachedManager(DuiLib::CPaintManagerUI* pManager) { g_pDetachedManager = pManager; }
  99. DuiLib::CPaintManagerUI* GetDetachedManager() { return g_pDetachedManager; }
  100. // -------------------------------------------------------------------------
  101. // 【R49d · 临时诊断】"ESC / 回车到底被谁收走了" —— 线程局部 WH_GETMESSAGE 嗅探。
  102. //
  103. // 真模态弹框的 ESC / 回车有三条消失路径,前两条在窗口过程里能看见,
  104. // **第三条只有在"消息出队、但还没被派发"时才能看见**:
  105. // ① DispatchMessage 直接派发给弹框
  106. // (我们回答 WM_GETDLGCODE = DLGC_WANTALLKEYS 之后就应该走这条);
  107. // ② 被 IsDialogMessage 改写成 WM_COMMAND(IDCANCEL / IDOK)
  108. // (__DialogWndProc 里再翻译回合成的 WM_KEYDOWN);
  109. // ③ 压根没投给弹框 —— 键盘焦点跑去了别的窗口(主窗口 / 屏幕键盘 / 别的进程)。
  110. // WH_GETMESSAGE 钩子正好卡在 GetMessage 返回之前,是唯一能分辨 ③ 的观察点。
  111. //
  112. // 只在真模态弹框存活期间安装(引用计数,嵌套弹框只装一次),只记录
  113. // ESC / 回车 / Tab 三个键 ⇒ 日志量极小。输出:exe 同目录 dlgkey_trace.log。
  114. //
  115. // ⚠️ 纯诊断,与业务逻辑无关。问题定位后整体删除:
  116. // 连同 SpawnKeySpy / StopKeySpy 两个调用点、以及 UIDiag 里的 LogKey 一起删。
  117. // -------------------------------------------------------------------------
  118. int g_nKeySpyRef = 0;
  119. HHOOK g_hKeySpy = NULL;
  120. LRESULT CALLBACK KeySpyProc(int nCode, WPARAM wParam, LPARAM lParam)
  121. {
  122. if (nCode == HC_ACTION)
  123. {
  124. const MSG* pMsg = reinterpret_cast<const MSG*>(lParam);
  125. if (pMsg != NULL && (pMsg->message == WM_KEYDOWN || pMsg->message == WM_SYSKEYDOWN))
  126. {
  127. const unsigned nKey = (unsigned)pMsg->wParam;
  128. if (nKey == VK_ESCAPE || nKey == VK_RETURN || nKey == VK_TAB)
  129. {
  130. TCHAR szCls[64] = { 0 };
  131. ::GetClassName(pMsg->hwnd, szCls, 63);
  132. DuiLibDiag::LogKey(
  133. _T("[SPY] key=0x%02X target=%p cls=%s focus=%p active=%p fg=%p"),
  134. nKey, pMsg->hwnd, szCls,
  135. ::GetFocus(), ::GetActiveWindow(), ::GetForegroundWindow());
  136. }
  137. }
  138. }
  139. return ::CallNextHookEx(g_hKeySpy, nCode, wParam, lParam);
  140. }
  141. void SpawnKeySpy()
  142. {
  143. ++g_nKeySpyRef;
  144. if (g_hKeySpy == NULL)
  145. {
  146. g_hKeySpy = ::SetWindowsHookEx(WH_GETMESSAGE, KeySpyProc, NULL, ::GetCurrentThreadId());
  147. DuiLibDiag::LogKey(_T("[SPY] install ref=%d hook=%p tid=%u"),
  148. g_nKeySpyRef, g_hKeySpy, (unsigned)::GetCurrentThreadId());
  149. }
  150. }
  151. void StopKeySpy()
  152. {
  153. if (g_nKeySpyRef > 0) --g_nKeySpyRef;
  154. if (g_nKeySpyRef <= 0)
  155. {
  156. g_nKeySpyRef = 0;
  157. if (g_hKeySpy != NULL)
  158. {
  159. ::UnhookWindowsHookEx(g_hKeySpy);
  160. DuiLibDiag::LogKey(_T("[SPY] uninstall hook=%p"), g_hKeySpy);
  161. g_hKeySpy = NULL;
  162. }
  163. }
  164. }
  165. // 【已移除】IsModalBlockedInputMessage —— 假模态时代的"在消息循环里丢弃发给 owner 的输入消息"。
  166. // 现在 ShowModal 走真模态(DialogBoxIndirectParam),系统对话框管理器自带 owner 禁用,
  167. // 这条兜底不但多余,还会误伤:真模态期间 owner 本就需要收到 WM_ACTIVATE/WM_KILLFOCUS 之类的
  168. // 通知消息,无差别丢弃会让 owner 的状态机错乱。故整段删除,不再保留。
  169. }
  170. // ---------------------------------------------------------------------------
  171. // 真模态(DialogBox)辅助:按 paint window 找到对应的 CPaintManagerUI。
  172. //
  173. // ⚠️ 必须定义在**匿名 namespace 之外**(即 DuiLib 命名空间直接成员),
  174. // 因为 UIBase.h 里有一份同名声明供 UIManager.cpp 使用。若同时存在匿名
  175. // namespace 内的一份,调用点会因两个候选而报 C2668「调用不明确」。
  176. //
  177. // 用途:
  178. // · ShowModal 里要把 manager 从旧 HWND 迁到对话框新 HWND 时需要先找到它;
  179. // · CPaintManagerUI::IsModalBackdropWindow 判定"本句柄是否就是弹框自己的
  180. // manager 窗口"时也依赖它(透传闸门的一条关键判据)。
  181. // 实现:遍历 CPaintManagerUI::GetPaintManagers(),比对 GetPaintWindow()。
  182. // ---------------------------------------------------------------------------
  183. DuiLib::CPaintManagerUI* FindManagerByPaintWindow(HWND hWnd)
  184. {
  185. if (hWnd == NULL) return NULL;
  186. CStdPtrArray* pManagers = DuiLib::CPaintManagerUI::GetPaintManagers();
  187. if (pManagers == NULL) return NULL;
  188. for (int i = 0; i < pManagers->GetSize(); ++i)
  189. {
  190. DuiLib::CPaintManagerUI* pManager = static_cast<DuiLib::CPaintManagerUI*>(pManagers->GetAt(i));
  191. if (pManager != NULL && pManager->GetPaintWindow() == hWnd) return pManager;
  192. }
  193. return NULL;
  194. }
  195. /////////////////////////////////////////////////////////////////////////////////////
  196. //
  197. //
  198. void UILIB_API DUI__Trace(LPCTSTR pstrFormat, ...)
  199. {
  200. #ifdef _DEBUG
  201. TCHAR szBuffer[2048] = {0};
  202. va_list args;
  203. va_start(args, pstrFormat);
  204. _vsntprintf(szBuffer, 2048, pstrFormat, args);
  205. va_end(args);
  206. CDuiString strMsg = szBuffer;
  207. strMsg += _T("\n");
  208. OutputDebugString(strMsg.GetData());
  209. #endif
  210. }
  211. LPCTSTR DUI__TraceMsg(UINT uMsg)
  212. {
  213. #define MSGDEF(x) if(uMsg==x) return _T(#x)
  214. MSGDEF(WM_SETCURSOR);
  215. MSGDEF(WM_NCHITTEST);
  216. MSGDEF(WM_NCPAINT);
  217. MSGDEF(WM_PAINT);
  218. MSGDEF(WM_ERASEBKGND);
  219. MSGDEF(WM_NCMOUSEMOVE);
  220. MSGDEF(WM_MOUSEMOVE);
  221. MSGDEF(WM_MOUSELEAVE);
  222. MSGDEF(WM_MOUSEHOVER);
  223. MSGDEF(WM_NOTIFY);
  224. MSGDEF(WM_COMMAND);
  225. MSGDEF(WM_MEASUREITEM);
  226. MSGDEF(WM_DRAWITEM);
  227. MSGDEF(WM_LBUTTONDOWN);
  228. MSGDEF(WM_LBUTTONUP);
  229. MSGDEF(WM_LBUTTONDBLCLK);
  230. MSGDEF(WM_RBUTTONDOWN);
  231. MSGDEF(WM_RBUTTONUP);
  232. MSGDEF(WM_RBUTTONDBLCLK);
  233. MSGDEF(WM_SETFOCUS);
  234. MSGDEF(WM_KILLFOCUS);
  235. MSGDEF(WM_MOVE);
  236. MSGDEF(WM_SIZE);
  237. MSGDEF(WM_SIZING);
  238. MSGDEF(WM_MOVING);
  239. MSGDEF(WM_GETMINMAXINFO);
  240. MSGDEF(WM_CAPTURECHANGED);
  241. MSGDEF(WM_WINDOWPOSCHANGED);
  242. MSGDEF(WM_WINDOWPOSCHANGING);
  243. MSGDEF(WM_NCCALCSIZE);
  244. MSGDEF(WM_NCCREATE);
  245. MSGDEF(WM_NCDESTROY);
  246. MSGDEF(WM_TIMER);
  247. MSGDEF(WM_KEYDOWN);
  248. MSGDEF(WM_KEYUP);
  249. MSGDEF(WM_CHAR);
  250. MSGDEF(WM_SYSKEYDOWN);
  251. MSGDEF(WM_SYSKEYUP);
  252. MSGDEF(WM_SYSCOMMAND);
  253. MSGDEF(WM_SYSCHAR);
  254. MSGDEF(WM_VSCROLL);
  255. MSGDEF(WM_HSCROLL);
  256. MSGDEF(WM_CHAR);
  257. MSGDEF(WM_SHOWWINDOW);
  258. MSGDEF(WM_PARENTNOTIFY);
  259. MSGDEF(WM_CREATE);
  260. MSGDEF(WM_NCACTIVATE);
  261. MSGDEF(WM_ACTIVATE);
  262. MSGDEF(WM_ACTIVATEAPP);
  263. MSGDEF(WM_CLOSE);
  264. MSGDEF(WM_DESTROY);
  265. MSGDEF(WM_GETICON);
  266. MSGDEF(WM_GETTEXT);
  267. MSGDEF(WM_GETTEXTLENGTH);
  268. static TCHAR szMsg[10];
  269. ::wsprintf(szMsg, _T("0x%04X"), uMsg);
  270. return szMsg;
  271. }
  272. /////////////////////////////////////////////////////////////////////////////////////
  273. //
  274. //
  275. //////////////////////////////////////////////////////////////////////////
  276. //
  277. DUI_BASE_BEGIN_MESSAGE_MAP(CNotifyPump)
  278. DUI_END_MESSAGE_MAP()
  279. static const DUI_MSGMAP_ENTRY* DuiFindMessageEntry(const DUI_MSGMAP_ENTRY* lpEntry,TNotifyUI& msg )
  280. {
  281. CDuiString sMsgType = msg.sType;
  282. CDuiString sCtrlName = msg.pSender->GetName();
  283. const DUI_MSGMAP_ENTRY* pMsgTypeEntry = NULL;
  284. while (lpEntry->nSig != DuiSig_end)
  285. {
  286. if(lpEntry->sMsgType==sMsgType)
  287. {
  288. if(!lpEntry->sCtrlName.IsEmpty())
  289. {
  290. if(lpEntry->sCtrlName==sCtrlName)
  291. {
  292. return lpEntry;
  293. }
  294. }
  295. else
  296. {
  297. pMsgTypeEntry = lpEntry;
  298. }
  299. }
  300. lpEntry++;
  301. }
  302. return pMsgTypeEntry;
  303. }
  304. bool CNotifyPump::AddVirtualWnd(CDuiString strName,CNotifyPump* pObject)
  305. {
  306. if( m_VirtualWndMap.Find(strName) == NULL )
  307. {
  308. m_VirtualWndMap.Insert(strName.GetData(),(LPVOID)pObject);
  309. return true;
  310. }
  311. return false;
  312. }
  313. bool CNotifyPump::RemoveVirtualWnd(CDuiString strName)
  314. {
  315. if( m_VirtualWndMap.Find(strName) != NULL )
  316. {
  317. m_VirtualWndMap.Remove(strName);
  318. return true;
  319. }
  320. return false;
  321. }
  322. bool CNotifyPump::LoopDispatch(TNotifyUI& msg)
  323. {
  324. const DUI_MSGMAP_ENTRY* lpEntry = NULL;
  325. const DUI_MSGMAP* pMessageMap = NULL;
  326. #ifndef UILIB_STATIC
  327. for(pMessageMap = GetMessageMap(); pMessageMap!=NULL; pMessageMap = (*pMessageMap->pfnGetBaseMap)())
  328. #else
  329. for(pMessageMap = GetMessageMap(); pMessageMap!=NULL; pMessageMap = pMessageMap->pBaseMap)
  330. #endif
  331. {
  332. #ifndef UILIB_STATIC
  333. ASSERT(pMessageMap != (*pMessageMap->pfnGetBaseMap)());
  334. #else
  335. ASSERT(pMessageMap != pMessageMap->pBaseMap);
  336. #endif
  337. if ((lpEntry = DuiFindMessageEntry(pMessageMap->lpEntries,msg)) != NULL)
  338. {
  339. goto LDispatch;
  340. }
  341. }
  342. return false;
  343. LDispatch:
  344. union DuiMessageMapFunctions mmf;
  345. mmf.pfn = lpEntry->pfn;
  346. bool bRet = false;
  347. int nSig;
  348. nSig = lpEntry->nSig;
  349. switch (nSig)
  350. {
  351. default:
  352. ASSERT(FALSE);
  353. break;
  354. case DuiSig_lwl:
  355. (this->*mmf.pfn_Notify_lwl)(msg.wParam,msg.lParam);
  356. bRet = true;
  357. break;
  358. case DuiSig_vn:
  359. (this->*mmf.pfn_Notify_vn)(msg);
  360. bRet = true;
  361. break;
  362. }
  363. return bRet;
  364. }
  365. void CNotifyPump::NotifyPump(TNotifyUI& msg)
  366. {
  367. ///遍历虚拟窗口
  368. if( !msg.sVirtualWnd.IsEmpty() ){
  369. for( int i = 0; i< m_VirtualWndMap.GetSize(); i++ ) {
  370. if( LPCTSTR key = m_VirtualWndMap.GetAt(i) ) {
  371. if( _tcsicmp(key, msg.sVirtualWnd.GetData()) == 0 ){
  372. CNotifyPump* pObject = static_cast<CNotifyPump*>(m_VirtualWndMap.Find(key, false));
  373. if( pObject && pObject->LoopDispatch(msg) )
  374. return;
  375. }
  376. }
  377. }
  378. }
  379. ///
  380. //遍历主窗口
  381. LoopDispatch( msg );
  382. }
  383. //////////////////////////////////////////////////////////////////////////
  384. ///
  385. 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()
  386. {
  387. }
  388. HWND CWindowWnd::GetHWND() const
  389. {
  390. return m_hWnd;
  391. }
  392. UINT CWindowWnd::GetClassStyle() const
  393. {
  394. return 0;
  395. }
  396. LPCTSTR CWindowWnd::GetSuperClassName() const
  397. {
  398. return NULL;
  399. }
  400. CWindowWnd::operator HWND() const
  401. {
  402. return m_hWnd;
  403. }
  404. void CWindowWnd::EnableUnicode()
  405. {
  406. m_bUnicode = true;
  407. }
  408. HWND CWindowWnd::CreateDuiWindow( HWND hwndParent, LPCTSTR pstrWindowName,DWORD dwStyle /*=0*/, DWORD dwExStyle /*=0*/ )
  409. {
  410. return Create(hwndParent,pstrWindowName,dwStyle,dwExStyle,0,0,0,0,NULL);
  411. }
  412. HWND CWindowWnd::Create(HWND hwndParent, LPCTSTR pstrName, DWORD dwStyle, DWORD dwExStyle, const RECT rc, HMENU hMenu)
  413. {
  414. return Create(hwndParent, pstrName, dwStyle, dwExStyle, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, hMenu);
  415. }
  416. HWND CWindowWnd::Create(HWND hwndParent, LPCTSTR pstrName, DWORD dwStyle, DWORD dwExStyle, int x, int y, int cx, int cy, HMENU hMenu)
  417. {
  418. if( GetSuperClassName() != NULL && !RegisterSuperclass() ) return NULL;
  419. if( GetSuperClassName() == NULL && !RegisterWindowClass() ) return NULL;
  420. if(m_bUnicode) {
  421. #ifndef UNICODE
  422. LPWSTR lpClassName = a2w((char*)GetWindowClassName());
  423. LPWSTR lpName = a2w((char*)pstrName);
  424. #else
  425. LPWSTR lpClassName = (LPWSTR)GetWindowClassName();
  426. LPWSTR lpName = (LPWSTR)pstrName;
  427. #endif
  428. m_hWnd = ::CreateWindowExW(dwExStyle, lpClassName, lpName, dwStyle, x, y, cx, cy, hwndParent, hMenu, CPaintManagerUI::GetInstance(), this);
  429. #ifndef UNICODE
  430. delete []lpClassName;
  431. delete []lpName;
  432. #endif
  433. }
  434. else {
  435. m_hWnd = ::CreateWindowEx(dwExStyle, GetWindowClassName(), pstrName, dwStyle, x, y, cx, cy, hwndParent, hMenu, CPaintManagerUI::GetInstance(), this);
  436. }
  437. ASSERT(m_hWnd!=NULL);
  438. RegisterTouchWindowIfAvailable(m_hWnd);
  439. return m_hWnd;
  440. }
  441. HWND CWindowWnd::Subclass(HWND hWnd)
  442. {
  443. ASSERT(::IsWindow(hWnd));
  444. ASSERT(m_hWnd==NULL);
  445. m_OldWndProc = SubclassWindow(hWnd, __WndProc);
  446. if( m_OldWndProc == NULL ) return NULL;
  447. m_bSubclassed = true;
  448. m_hWnd = hWnd;
  449. ::SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LPARAM>(this));
  450. RegisterTouchWindowIfAvailable(m_hWnd);
  451. return m_hWnd;
  452. }
  453. void CWindowWnd::Unsubclass()
  454. {
  455. ASSERT(::IsWindow(m_hWnd));
  456. if( !::IsWindow(m_hWnd) ) return;
  457. if( !m_bSubclassed ) return;
  458. SubclassWindow(m_hWnd, m_OldWndProc);
  459. m_OldWndProc = ::DefWindowProc;
  460. m_bSubclassed = false;
  461. }
  462. void CWindowWnd::ShowWindow(bool bShow /*= true*/, bool bTakeFocus /*= false*/)
  463. {
  464. ASSERT(::IsWindow(m_hWnd));
  465. if( !::IsWindow(m_hWnd) ) return;
  466. ::ShowWindow(m_hWnd, bShow ? (bTakeFocus ? SW_SHOWNORMAL : SW_SHOWNOACTIVATE) : SW_HIDE);
  467. }
  468. UINT CWindowWnd::ShowModal()
  469. {
  470. // =========================================================================
  471. // 【真模态】改用 Windows 的对话框机制(DialogBoxIndirectParam)驱动模态。
  472. //
  473. // 参考:网易 nim_duilib 的 NativeWindow_Windows::DoModal()
  474. // (duilib/Core/NativeWindow_Windows.cpp)
  475. //
  476. // ── 为什么必须换掉旧写法 ──────────────────────────────────────────────
  477. // 旧实现是"假模态":自己 ShowWindow + EnableWindow(owner,FALSE) + TOPMOST +
  478. // 抢前台 + 自写 while(GetMessage) 嵌套循环。在 Win10 触摸屏上必然失败,根因是
  479. // 触摸消息的投递目标规则(MSDN, Getting Started with Windows Touch Messages):
  480. //
  481. // "WM_TOUCH messages are currently 'greedy.' After the first touch message is
  482. // received on a window, all touch messages are sent to that window until
  483. // another window receives focus."
  484. //
  485. // 主窗口早就 RegisterTouchWindow 过、也早就收到过 WM_TOUCH,所以触摸归属权长期
  486. // 在它手里;弹框是自己 Create 出来的普通窗口,**不是**系统的触摸目标。
  487. // 我们为此打了一堆补丁(手动焦点切换、刷新注册、消息循环转发、__WndProc 转发兜底),
  488. // 实测全部无效:日志里弹框始终零 WM_TOUCH、零 WM_LBUTTONDOWN/UP。
  489. //
  490. // ── 对话框为什么能解决 ────────────────────────────────────────────────
  491. // DialogBox 是内核实现的模态机制,它:
  492. // · 由系统跑消息循环(不需要我们自写嵌套循环);
  493. // · 由系统按正确时机禁用 owner(不需要我们 EnableWindow);
  494. // · 由系统维持前台/激活(不需要 TOPMOST / SetForegroundWindow / 焦点切换);
  495. // · **把对话框窗口本身变成输入目标**,触摸自然投给它、提升的鼠标消息也落在它身上。
  496. //
  497. // 于是"贪婪归属权""提升鼠标被吞""owner 抢前台""禁用窗口收不到消息"这几类问题
  498. // 全部不存在 —— 不是被绕过,而是压根不成立。
  499. //
  500. // ── 如何与既有代码兼容 ────────────────────────────────────────────────
  501. // 调用方约定是:new → Create(hwnd, ..., UI_WNDSTYLE_DIALOG, ...) → SetIcon →
  502. // CenterWindow() → ShowModal()。也就是说**窗口在我们被调用之前就已经 Create 好了**,
  503. // CPaintManagerUI 也已 Init 到那个 HWND 上。所以这里不能另起一个对话框窗口,
  504. // 而是:用当前窗口的几何/样式造一个 DLGTEMPLATE,让 DialogBoxIndirectParam 去创建
  505. // 对话框窗口,再把旧的 HWND 销毁、把 DuiLib 的 manager 迁移到新 HWND —— 这样
  506. // 调用方完全无感知,`m_hWnd` 语义不变,30+ 个调用点一行都不用改。
  507. // =========================================================================
  508. ASSERT(::IsWindow(m_hWnd));
  509. if( !::IsWindow(m_hWnd) ) return 0;
  510. HWND hWndOwner = ::GetWindow(m_hWnd, GW_OWNER);
  511. if( hWndOwner == NULL ) hWndOwner = ::GetActiveWindow();
  512. // ---- 1) 采集旧窗口的几何与样式,用于构造对话框模板 ----
  513. RECT rcWnd = { 0 };
  514. ::GetWindowRect(m_hWnd, &rcWnd);
  515. const int nW = rcWnd.right - rcWnd.left;
  516. const int nH = rcWnd.bottom - rcWnd.top;
  517. // 【尺寸精确性】旧窗口的客户区尺寸 —— 这是迁移后必须保持不变的量。
  518. //
  519. // 为什么不能直接用窗口矩形(nW/nH)去填 DLGTEMPLATE.cx/cy:
  520. // 1. DLGTEMPLATE 的 cx/cy 描述的是**客户区**尺寸,不是窗口外框尺寸;
  521. // 2. 我们给模板设的样式(WS_CAPTION/WS_DLGFRAME/DS_MODALFRAME…)会让系统
  522. // 在此基础上再往外加一圈非客户区边框。
  523. // 若直接填 nW/nH,新窗口 = 旧窗口 + 边框 ⇒ 弹框整体变大、位置也偏。
  524. //
  525. // 因此这里取"旧窗口客户区尺寸"作为目标客户区尺寸;创建完成后再在 __DialogProc 里
  526. // 用 AdjustWindowRectEx 反算目标外框并 SetWindowPos 精确校正(见 __DialogProc)。
  527. RECT rcClient = { 0 };
  528. ::GetClientRect(m_hWnd, &rcClient);
  529. const int nClientW = rcClient.right - rcClient.left;
  530. const int nClientH = rcClient.bottom - rcClient.top;
  531. DWORD dwStyle = (DWORD)::GetWindowLong(m_hWnd, GWL_STYLE);
  532. DWORD dwExStyle = (DWORD)::GetWindowLong(m_hWnd, GWL_EXSTYLE);
  533. // 对话框专用的样式:把我们自己那套 WS_POPUPWINDOW/WS_CAPTION/WS_DLGFRAME 换成
  534. // 对话框该有的组合。WS_POPUP 必须保留(否则会变成带系统标题栏的普通窗口)。
  535. // WS_VISIBLE 让 DialogBox 创建后直接显示。
  536. //
  537. // ⚠️ 关于 WS_CAPTION / DS_MODALFRAME:
  538. // 本项目的弹框(UI_WNDSTYLE_DIALOG)是**无系统标题栏的自绘弹框**,且调用方
  539. // 均未传 WS_THICKFRAME(固定尺寸)。所以这里**不能**自作主张加 DS_MODALFRAME 或
  540. // WS_THICKFRAME —— 那会给弹框加一圈可调整大小的边框,既改变外观又改变尺寸。
  541. // 保持 WS_POPUP + 原有的 WS_CAPTION/WS_SYSMENU 即可(自绘弹框本来就不显示系统标题栏)。
  542. DWORD dwDlgStyle = WS_POPUP | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
  543. if( dwStyle & WS_CAPTION ) dwDlgStyle |= WS_CAPTION | WS_SYSMENU;
  544. if( dwStyle & WS_THICKFRAME ) dwDlgStyle |= WS_THICKFRAME;
  545. if( dwStyle & WS_MINIMIZEBOX ) dwDlgStyle |= WS_MINIMIZEBOX;
  546. if( dwStyle & WS_MAXIMIZEBOX ) dwDlgStyle |= WS_MAXIMIZEBOX;
  547. // ex-style 基本原样透传(WS_EX_TOOLWINDOW / WS_EX_WINDOWEDGE 等),但清掉两类:
  548. // · WS_EX_APPWINDOW —— 对话框不该出现在任务栏;
  549. // · WS_EX_DLGMODALFRAME —— 会给弹框加一圈双线边框,固定尺寸自绘弹框不需要(会改变外框尺寸)。
  550. dwExStyle &= ~(WS_EX_APPWINDOW | WS_EX_DLGMODALFRAME);
  551. // ---- 2) 构造 DLGTEMPLATE 资源 ----
  552. // 只用 DLGTEMPLATE 本体(不含菜单/类名/标题/字体),微软文档允许这样,
  553. // 系统会套用默认的对话框字体与类(#32770)。
  554. // 参考 nim_duilib:sizeof(DLGTEMPLATE) + 32 字节余量。
  555. const size_t nDataSize = sizeof(DLGTEMPLATE) + 32;
  556. HGLOBAL hResource = ::GlobalAlloc(GMEM_MOVEABLE, nDataSize);
  557. if( hResource == NULL )
  558. {
  559. return 0;
  560. }
  561. LPDLGTEMPLATE pTemplate = (LPDLGTEMPLATE)::GlobalLock(hResource);
  562. if( pTemplate == NULL )
  563. {
  564. ::GlobalFree(hResource);
  565. return 0;
  566. }
  567. ::ZeroMemory(pTemplate, nDataSize);
  568. pTemplate->style = dwDlgStyle;
  569. pTemplate->dwExtendedStyle = dwExStyle;
  570. pTemplate->cdit = 0; // 不使用模板内控件(控件由 DuiLib 自绘)
  571. // DLGTEMPLATE 的 cx/cy 单位是对话框单位(DLU),且语义为**客户区**尺寸。
  572. // 这里只是给系统一个初始值(避免 0 尺寸导致奇怪的默认值),真正的精确尺寸
  573. // 由 __DialogProc 里创建完成后按旧客户区尺寸 + AdjustWindowRectEx 反算并 SetWindowPos 校正。
  574. pTemplate->x = 0;
  575. pTemplate->y = 0;
  576. pTemplate->cx = (short)(nClientW > 0 ? nClientW : nW);
  577. pTemplate->cy = (short)(nClientH > 0 ? nClientH : nH);
  578. // ---- 3) 迁移前先把旧的 HWND 记录下来,并让 manager 做好切换准备 ----
  579. HWND hWndOld = m_hWnd;
  580. m_bDoModal = true;
  581. m_nModalRet = 0;
  582. // 把"目标位置 + 目标客户区尺寸"暂存到成员,供 __DialogProc 在创建完成后精确校正。
  583. // 之所以不在模板里一次算准:对话框的非客户区厚度取决于实际样式(WS_CAPTION/WS_DLGFRAME/
  584. // WS_EX_* 组合),预估值容易差几个像素;创建后用 AdjustWindowRectEx 反算再 SetWindowPos
  585. // 是唯一能保证"客户区尺寸与旧窗口逐像素一致"的做法。
  586. m_rcModalTarget.left = rcWnd.left;
  587. m_rcModalTarget.top = rcWnd.top;
  588. m_rcModalTarget.right = rcWnd.left + nClientW;
  589. m_rcModalTarget.bottom = rcWnd.top + nClientH;
  590. // 记录"当前模态窗口",供库内其它地方判断(如 OnTouchInput 的老兜底逻辑)。
  591. // 注意:此时还不知道新 HWND,__DialogProc 里会再次 SetModalWindow。
  592. HWND hPrevModal = CPaintManagerUI::GetModalWindow();
  593. // 关键:先把旧窗口销毁、把 manager 解绑,再让 DialogBox 创建新的对话框窗口,
  594. // 这样 manager 不会出现"两个 HWND 同时活着"的错乱状态。
  595. // 说明:调用方是在 ShowModal() 之前 Create 的,所以这里必须主动清理旧窗口。
  596. HWND hWndOldCaptured = m_hWnd;
  597. m_hWnd = NULL; // 先置空:__DialogProc 会在 WM_INITDIALOG 里填入新句柄
  598. // 把 manager 从旧窗口上摘下来(保留它的控件树/资源,只换 paint window)
  599. CPaintManagerUI* pManager = FindManagerByPaintWindow(hWndOldCaptured);
  600. SetDetachedManager(pManager);
  601. if( pManager != NULL )
  602. {
  603. pManager->DetachPaintWindow();
  604. }
  605. // ---------------------------------------------------------------------
  606. // ⚠️⚠️⚠️【第 45 轮 · 悬垂 this 根治】销毁"壳窗口"之前,必须先把它的
  607. // GWLP_USERDATA 摘掉,否则本对象会被自己删掉。
  608. //
  609. // 机制(真机断言实证,崩溃点在 UIBase.cpp:674):
  610. // 这个壳窗口是本对象 `Create` 出来的 —— CWindowWnd::Create 走
  611. // `CreateWindowEx(..., this)`,__WndProc 的 WM_NCCREATE 会把 `this`
  612. // 写进该 HWND 的 GWLP_USERDATA。于是 ::DestroyWindow 时
  613. // __WndProc 必走 WM_NCDESTROY 分支 ⇒ `pThis->OnFinalMessage(hWnd)`。
  614. // 而**业务侧 40+ 个弹框类都把 OnFinalMessage 实现成 `delete this`**
  615. // (例:zhipuzi_pos_windows/wnd/CChufangSettingWnd.h:32):
  616. // void OnFinalMessage(HWND) { /*WindowImplBase::...*/ m_pm.RemovePreMessageFilter(this); delete this; }
  617. // ⇒ 对象在本函数**还没跑完**时就被自我销毁,随后
  618. // `DialogBoxIndirectParam(..., (LPARAM)this)` ← 第 674 行那个断言的现场
  619. // 交出去的是**已释放对象的指针**;__DialogProc 读 `pThis->m_hWnd` 拿到的是
  620. // Debug 堆 free 填充 0xDDDDDDDD(≠ NULL)⇒ `ASSERT(pThis->m_hWnd == NULL)` 失败。
  621. // (调用栈:CSettingPageUI::HandleClickMsg:1512 → ShowModal:630 → __DialogProc)
  622. //
  623. // 为什么"只摘 USERDATA"就够(不新增任何机制):
  624. // `delete this` 的职责**已经转移到对话框窗口**上 ——
  625. // __DialogProc 会把 `this` 写进**对话框**的 GWLP_USERDATA,
  626. // 对话框销毁时由 __DialogWndProc 的 WM_NCDESTROY 调 OnFinalMessage ⇒ 仍然只删一次。
  627. // 壳窗口被摘掉 USERDATA 后销毁时,__WndProc 里 `pThis == NULL` ⇒ 直接走
  628. // DefWindowProc,不会触发第二次 OnFinalMessage(无双重释放)。
  629. // 壳窗口此时已经没有存在价值:m_hWnd 已置空、manager 已 DetachPaintWindow、
  630. // 控件树已由 AttachPaintWindow 跟着 manager 搬到对话框。
  631. // ---------------------------------------------------------------------
  632. if( ::IsWindow(hWndOldCaptured) )
  633. {
  634. ::SetWindowLongPtr(hWndOldCaptured, GWLP_USERDATA, 0L);
  635. ::DestroyWindow(hWndOldCaptured);
  636. }
  637. // ---- 【Win7"咚"声 · 根治】把"在途的提升鼠标副本"从消息队列里摘掉 ----
  638. //
  639. // 时序(真机日志实证):
  640. // 调用方的触摸 UP 已经消费完(**当前正在执行的这段代码就是它的点击回调**),
  641. // 但同一次手势被系统提升出来的鼠标副本**还排在消息队列里没被投出来**;
  642. // 紧接着下面 DialogBoxIndirectParam 会把 owner `EnableWindow(FALSE)` 禁用,
  643. // 之后那条副本才被投递 ⇒ 撞上"点击禁用窗口"的默认处理 ⇒ **MessageBeep(0) 咚**。
  644. //
  645. // 为什么必须在这里摘、而不是在窗口过程里拦:
  646. // · 消息根本进不了窗口过程(系统在输入阶段就按"点击了禁用窗口"处理掉,消息根本不会到达窗口过程)
  647. // ⇒ 写在 MessageHandler 里的任何闸门,判据再准也无从生效;
  648. // · 此处摘除与"创建弹框"之间**没有任何消息泵** ⇒ 摘除对投递而言是**原子**的,
  649. // 不存在"刚摘完又被投一条"的窗口期。
  650. //
  651. // 摘除的是"**owner 窗口**的鼠标类消息",且只在"触摸通道确实收到了本次手势"时摘
  652. // (判据见 IsPromotedMouseDuplicatePending)—— 它们本来就是重复的:这次点击已经
  653. // 由触摸通道的 tap 判定处理完毕(否则走不到 ShowModal 这里)。因此两条边界都守住:
  654. // · 点主窗口控件点出弹框 ⇒ 摘掉 ⇒ **无声**(用户要求消除的那一声);
  655. // · 弹框已打开后点弹框外空白区 ⇒ 那处的 WM_TOUCH 在**输入阶段**就被系统丢弃、
  656. // 触摸通道根本没收到手势 ⇒ 标志为假 ⇒ 不摘 ⇒ **那一声"咚"照旧保留**
  657. // (用户明确要求不改的那一条边界)。
  658. if( hWndOwner != NULL && CPaintManagerUI::IsPromotedMouseDuplicatePending() )
  659. {
  660. CPaintManagerUI::DropQueuedPromotedMouseMessages(hWndOwner);
  661. }
  662. // ---- 4) 交给系统对话框管理器跑模态循环 ----
  663. // 这是阻塞调用,返回时模态已结束。
  664. INT_PTR nRet = ::DialogBoxIndirectParam(CPaintManagerUI::GetInstance(),
  665. pTemplate,
  666. hWndOwner,
  667. CWindowWnd::__DialogProc,
  668. (LPARAM)this);
  669. // ---------------------------------------------------------------------
  670. // ⚠️⚠️【第 45 轮】从这里往下**只能碰局部变量与静态函数**,绝不能再读写本对象的成员。
  671. //
  672. // 为什么:DialogBoxIndirectParam 返回时,对话框窗口已经销毁 ——
  673. // __DialogWndProc 的 WM_NCDESTROY 已经调过 `pThis->OnFinalMessage(hWnd)`,
  674. // 而业务侧弹框类的 OnFinalMessage 是 `delete this`
  675. // ⇒ 本对象此刻**已经被释放**。
  676. // 原来这里的 `m_bDoModal = false;` 与 `return m_nModalRet;` 就是在已释放内存上
  677. // 读写(一直没炸,只因那块堆内存尚未被复用),属同源隐患,一并收掉。
  678. //
  679. // 返回值从哪来:__DialogWndProc → Close() 里是 `::EndDialog(m_hWnd, (INT_PTR)nRet)`,
  680. // 而 DialogBoxIndirectParam 的返回值**就是**那个 nResult,与 m_nModalRet 恒等
  681. // ⇒ 直接取局部变量 nRet 即可,无需回头读成员。
  682. //
  683. // m_bDoModal 为什么正常路径不用复位:需要它的那个对象此刻已被销毁;若某个弹框类
  684. // 不删自己,它下次 ShowModal 的开头会重新 `m_bDoModal = true`,语义不变。
  685. // ---------------------------------------------------------------------
  686. const UINT nModalRetVal = ( nRet == -1 ) ? 0u : (UINT)nRet;
  687. // 资源可以释放了(DialogBoxIndirectParam 内部已把模板复制走)——
  688. // 但谨慎起见放在 EndDialog 之后,与 nim_duilib 一致。
  689. ::GlobalUnlock(hResource);
  690. ::GlobalFree(hResource);
  691. // 兜底:若 __DialogProc 因故没跑(创建失败 / WM_INITDIALOG 里 early-return),
  692. // 这里把中转变量清干净,避免悬垂指针影响后续 ShowModal。
  693. SetDetachedManager(NULL);
  694. CPaintManagerUI::SetModalWindow(hPrevModal);
  695. // DialogBoxIndirectParam 返回值:-1 表示创建失败
  696. if( nRet == -1 )
  697. {
  698. // 创建失败 ⇒ 对话框窗口从未存在 ⇒ __DialogProc 没跑 ⇒ 本对象还活着,
  699. // 这时才允许安全复位成员(否则下一次 ShowModal 会误判状态)。
  700. m_bDoModal = false;
  701. m_hWnd = NULL;
  702. return 0;
  703. }
  704. return nModalRetVal;
  705. }
  706. // -----------------------------------------------------------------------------
  707. // 真模态:对话框过程。
  708. //
  709. // 生命周期:
  710. // DialogBoxIndirectParam → 创建 #32770 窗口 → WM_INITDIALOG(此时才有真 HWND)
  711. // → 把 manager 挂到新 HWND、换掉 GWLP_WNDPROC
  712. // → return TRUE(表示我们已经处理了焦点)
  713. // -----------------------------------------------------------------------------
  714. INT_PTR CALLBACK CWindowWnd::__DialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
  715. {
  716. if( uMsg == WM_INITDIALOG )
  717. {
  718. // 这是对话框的第一个消息;lParam 是我们传进来的 this
  719. CWindowWnd* pThis = reinterpret_cast<CWindowWnd*>(lParam);
  720. if( pThis == NULL ) return TRUE;
  721. ASSERT(pThis->m_hWnd == NULL);
  722. pThis->m_hWnd = hDlg;
  723. // ---- 换掉对话框默认的窗口过程 ----
  724. // 这样后续所有消息(含 WM_PAINT / WM_TOUCH / 鼠标)都会走到
  725. // CWindowWnd::HandleMessage → CPaintManagerUI,DuiLib 的自绘与路由完全保留。
  726. ASSERT(pThis->m_pfnOldDlgWndProc == NULL);
  727. pThis->m_pfnOldDlgWndProc = (WNDPROC)::GetWindowLongPtr(hDlg, GWLP_WNDPROC);
  728. ::SetWindowLongPtr(hDlg, GWLP_WNDPROC, (LONG_PTR)(WNDPROC)CWindowWnd::__DialogWndProc);
  729. ::SetWindowLongPtr(hDlg, GWLP_USERDATA, (LONG_PTR)pThis);
  730. // ---- 登记为当前模态窗口 ----
  731. CPaintManagerUI::SetModalWindow(hDlg);
  732. // ---- 触摸注册:对话框是全新的 HWND,必须注册,否则收不到 WM_TOUCH ----
  733. RegisterTouchWindowIfAvailable(hDlg);
  734. // ---- 把旧的 CPaintManagerUI 挂到对话框窗口上 ----
  735. // 调用方在 ShowModal() 之前已 Create + manager.Init 到旧 HWND;这里把 manager 迁移过来,
  736. // 让 DuiLib 的全部既有逻辑(绘制 / 消息 / 控件树)在新窗口上继续工作。
  737. CPaintManagerUI* pManager = GetDetachedManager();
  738. if( pManager != NULL )
  739. {
  740. pManager->AttachPaintWindow(hDlg);
  741. // ★【R50】补一次"全新窗口"的首次布局 + 首次布局完成通知。
  742. // 壳窗口阶段 AttachDialog 已经把 m_bFirstLayout / m_bUpdateNeeded 置过 true,
  743. // 一旦在壳窗口那边被消费掉,弹框这个新 HWND 上就再也不会发 windowinit
  744. // ⇒ 业务层挂 windowinit 的初始化(默认焦点等)不执行。
  745. pManager->RewindForModalPaint();
  746. // ---- [窗口阴影] 在对话框的新 HWND 上重建阴影窗口 ----
  747. // 背景:CShadowUI 的阴影窗口(PerryShadowWnd)是**绑定父窗口 HWND** 的 ——
  748. // · Create() 把 (父HWND → CShadowUI*) 记进 GetShadowMap(),以该 HWND 为
  749. // owner 创建阴影窗口,并把父窗口的窗口过程换成 ParentProc
  750. // (UIShadow.cpp:73-105);
  751. // · 父窗口被销毁时,ParentProc 收到 WM_DESTROY → DestroyWindow(阴影窗口),
  752. // 收到 WM_NCDESTROY → GetShadowMap().erase(父HWND)(UIShadow.cpp:238-243)。
  753. // 而本函数上方的真模态流程恰好把弹框**原来的 HWND 销毁了**:
  754. // DetachPaintWindow() → ::DestroyWindow(hWndOldCaptured) →
  755. // DialogBoxIndirectParam()。于是弹框的阴影窗口连同映射记录一起消失;而全工程
  756. // 唯一的 m_shadow.Create() 调用点在 CPaintManagerUI::AttachDialog
  757. // (UIManager.cpp:4918),**真模态路径不走它**(这里只调 AttachPaintWindow,
  758. // 它只换 m_hWndPaint / m_hDcPaint)⇒ 30+ 个模态弹框从此全都没有阴影窗口。
  759. // 主窗口不经过 ShowModal,所以它的阴影还在 —— 这正是
  760. // 「除了主窗口,所有模态弹框的窗口阴影都没了」的根因。
  761. //
  762. // 为什么直接调 Create() 就够:阴影样式(shadowsize / shadowcolor /
  763. // shadowsharpness / shadowposition …)早在 XML 解析时就写进了 manager 的
  764. // m_shadow 对象,重建只需重新绑 HWND,无需再解析一次 XML。
  765. // Create() 内部自带 m_bIsShowShadow 判断,没开阴影的窗口自然跳过。
  766. pManager->GetShadow()->Create(pManager);
  767. // 用掉即清:避免这个文件级静态变量残留一个已失效的指针,
  768. // 影响下一次 ShowModal(尤其嵌套弹框:A 弹框里再开 B 弹框)。
  769. SetDetachedManager(NULL);
  770. }
  771. // ---- [WM_TOUCH 归属权] 立刻把"补调原窗口过程"的钩子绑到本弹框 ----
  772. // 为什么必须在这里(而不是等 __DialogWndProc 首次进入时再绑):
  773. // __DialogProc 在 WM_INITDIALOG 里直接调 pThis->HandleMessage(见下方),
  774. // 这条路径**不经过** __DialogWndProc,所以"首次进入窗口过程时绑定"
  775. // 在这条路上不会触发。而 AttachPaintWindow 之后 manager 已经指向 hDlg,
  776. // 一旦有 WM_MOUSEMOVE 到达就可能需要补调 ⇒ 必须现在就绑好。
  777. // 绑定对象是 m_pfnOldDlgWndProc(即 #32770 原过程 / DefDlgProc)。
  778. if( pManager != NULL )
  779. {
  780. // m_pfnOldDlgWndProc 本身就是 WNDPROC;只有它意外为空时才回落到 DefDlgProc
  781. // (签名是 INT_PTR 返回,需显式转换,不能与 WNDPROC 混在三目里)。
  782. WNDPROC pfnDlgTarget = pThis->m_pfnOldDlgWndProc;
  783. if( pfnDlgTarget == NULL )
  784. pfnDlgTarget = reinterpret_cast<WNDPROC>(::DefDlgProc);
  785. BindRelayTarget(pfnDlgTarget, hDlg);
  786. pManager->SetDefProcRelay(&RelayWndProc, hDlg);
  787. }
  788. // ---- 尺寸校正(关键)----
  789. // DLGTEMPLATE 里给的 cx/cy 是对话框单位(DLU)、且语义为「客户区」,系统创建时还会
  790. // 按实际样式额外加一圈非客户区边框。所以不能指望"模板尺寸 == 目标尺寸"。
  791. // 这里用 AdjustWindowRectEx 按对话框**真实**的样式反算目标外框,再 SetWindowPos 精确校正,
  792. // 保证客户区尺寸与原窗口逐像素一致 —— 这正是"弹框变大了"的修法。
  793. //
  794. // 放在 AttachPaintWindow 之后、HandleMessage(WM_INITDIALOG) 之前:
  795. // 此时 manager 已绑定新 HWND,尺寸变化会走正常的布局流程;且业务层 OnCreate 之前
  796. // 窗口就已经是最终尺寸,避免"先小后大"的闪动与一次性布局错位。
  797. {
  798. const DWORD dwRealStyle = (DWORD)::GetWindowLong(hDlg, GWL_STYLE);
  799. const DWORD dwRealExStyle = (DWORD)::GetWindowLong(hDlg, GWL_EXSTYLE);
  800. RECT rcAdjust = { 0, 0,
  801. pThis->m_rcModalTarget.right - pThis->m_rcModalTarget.left,
  802. pThis->m_rcModalTarget.bottom - pThis->m_rcModalTarget.top };
  803. const int nClientW = rcAdjust.right;
  804. const int nClientH = rcAdjust.bottom;
  805. // 反算:以"目标客户区尺寸"为输入,得到需要的外框尺寸
  806. ::AdjustWindowRectEx(&rcAdjust, dwRealStyle, FALSE, dwRealExStyle);
  807. const int nWndW = rcAdjust.right - rcAdjust.left;
  808. const int nWndH = rcAdjust.bottom - rcAdjust.top;
  809. RECT rcCur = { 0 };
  810. ::GetWindowRect(hDlg, &rcCur);
  811. // SWP_FRAMECHANGED 让系统重新计算非客户区(样式刚被 SetWindowLongPtr 换过)
  812. ::SetWindowPos(hDlg, NULL,
  813. pThis->m_rcModalTarget.left, pThis->m_rcModalTarget.top,
  814. nWndW, nWndH,
  815. SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
  816. RECT rcAfter = { 0 };
  817. ::GetClientRect(hDlg, &rcAfter);
  818. }
  819. // ---- [窗口阴影] 尺寸校正后再刷新一次 ----
  820. // 上面的阴影是在尺寸校正**之前**建的,那时 hDlg 还是 DLGTEMPLATE 的初始尺寸,
  821. // 阴影位图因此是旧几何;SetWindowPos 之后窗口尺寸才最终确定,这里补刷一次,
  822. // 保证阴影与弹框逐像素对齐,而不依赖后续 WM_PAINT 的补正时机。
  823. if( pManager != NULL )
  824. {
  825. pManager->UpdateShadow();
  826. }
  827. // 交给 CWindowWnd::HandleMessage 处理 WM_INITDIALOG,业务层的 OnCreate 等由此触发。
  828. pThis->HandleMessage(uMsg, wParam, lParam);
  829. ::SetFocus(hDlg);
  830. // [R49d 诊断] 装键盘嗅探钩子(引用计数;见 KeySpyProc 的注释)。
  831. SpawnKeySpy();
  832. return TRUE; // TRUE = 我们自己设置了焦点
  833. }
  834. return FALSE;
  835. }
  836. // -----------------------------------------------------------------------------
  837. // 真模态:对话框窗口的窗口过程。
  838. // 几乎就是 __WndProc 的对话框版本。
  839. // -----------------------------------------------------------------------------
  840. LRESULT CALLBACK CWindowWnd::__DialogWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
  841. {
  842. CWindowWnd* pThis = reinterpret_cast<CWindowWnd*>(::GetWindowLongPtr(hWnd, GWLP_USERDATA));
  843. if( uMsg == WM_NCDESTROY && pThis != NULL )
  844. {
  845. // [R49d 诊断] 弹框销毁即卸载键盘嗅探(引用计数归零才真正 Unhook)。
  846. StopKeySpy();
  847. // 顺序很关键:
  848. // 1) 先把窗口过程还原(此后系统调用会回到对话框默认过程);
  849. // 2) 再让 DefWindowProc 做标准清理;
  850. // 3) 清 GWLP_USERDATA —— 必须在 HandleMessage 之前,防止重入时又拿到 this;
  851. // 4) 清模态登记;
  852. // 5) m_hWnd 置空【要在 Unsubclass 之前】—— 注意本项目弹框并不 Subclass,
  853. // m_bSubclassed 恒为 false,这里只是保持与 __WndProc 一致的写法,
  854. // 并让 OnFinalMessage 里看到的 m_hWnd 为空(与旧行为一致)。
  855. if( pThis->m_pfnOldDlgWndProc != NULL )
  856. {
  857. ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)pThis->m_pfnOldDlgWndProc);
  858. pThis->m_pfnOldDlgWndProc = NULL;
  859. }
  860. LRESULT lRes = ::DefWindowProc(hWnd, uMsg, wParam, lParam);
  861. // [WM_TOUCH 归属权] 解绑转发目标,避免悬垂到已销毁的对话框窗口过程。
  862. {
  863. DuiLib::CPaintManagerUI* pMgrNc = FindManagerByPaintWindow(hWnd);
  864. if( pMgrNc != NULL )
  865. {
  866. pMgrNc->ClearDefProcRelay(hWnd);
  867. }
  868. BindRelayTarget(NULL, NULL);
  869. }
  870. ::SetWindowLongPtr(hWnd, GWLP_USERDATA, 0L);
  871. CPaintManagerUI::SetModalWindow(NULL);
  872. pThis->m_hWnd = NULL;
  873. if( pThis->m_bSubclassed ) pThis->Unsubclass();
  874. pThis->OnFinalMessage(hWnd);
  875. return lRes;
  876. }
  877. // WM_INITDIALOG 已经由 __DialogProc 处理并 return TRUE,
  878. // 一般不会再走到这里;若系统仍投递(例如 EndDialog 之后的收尾路径),
  879. // 直接交回对话框默认过程,避免被 HandleMessage 当成普通消息误处理。
  880. if( uMsg == WM_INITDIALOG )
  881. return ::DefDlgProc(hWnd, uMsg, wParam, lParam);
  882. if( pThis != NULL )
  883. {
  884. // -------------------------------------------------------------------------
  885. // ★★★【R49c】把「对话框级导航键」从对话框管理器手里要回来
  886. // —— 根治「真模态弹框 ESC / 回车完全没反应」。
  887. //
  888. // 【现象】真模态弹框(#32770)里按 ESC / 回车毫无反应;
  889. // 而全库 39 个弹框都把
  890. // 「WM_KEYDOWN + VK_ESCAPE / VK_RETURN」分支写在
  891. // IMessageFilterUI::MessageHandler(pre-filter)里,看上去一点问题都没有。
  892. //
  893. // 【根因】不是我们的代码,是 Windows 对话框管理器的既定行为:
  894. // DialogBoxIndirectParam 内部跑的是
  895. // while(GetMessage()) {
  896. // if (!IsDialogMessage(hDlg, &msg)) { TranslateMessage; DispatchMessage; }
  897. // }
  898. // ⇒ 键盘消息先过 IsDialogMessage。它会(参考 Raymond Chen
  899. // 《Exploring the signals the dialog manager uses for dismissing a dialog》):
  900. // · VK_ESCAPE -> 改写成 WM_COMMAND(wParam=LOWORD(IDCANCEL)=2, HIWORD=BN_CLICKED)
  901. // · VK_RETURN -> 改写成 WM_COMMAND(wParam=LOWORD(IDOK)=1, HIWORD=BN_CLICKED)
  902. // 然后 **return TRUE**(消息已被吃,不再 DispatchMessage)
  903. // ⇒ 窗口过程永远收不到这两个 WM_KEYDOWN。Petzold《Programming Windows》
  904. // 原文也点明:“there is no requirement that controls with IDOK or
  905. // IDCANCEL IDs exist” —— 即弹框一个子控件都没有(本项目
  906. // DLGTEMPLATE 的 cdit=0)也照样发。
  907. //
  908. // 【为什么上一轮(#46)没顺带修好】那一轮补的 PreMessageHandler
  909. // 只解决「PostMessage 过来的自定义消息(WM_SHANGPIN_HANDLE_SUCCESS 等)
  910. // 到不了 pre-filter」—— 那些是**投递消息**,不经过
  911. // IsDialogMessage;而 ESC / 回车是**键盘消息**,在更前面就被
  912. // 改写掉了。⇒ 现象永远对不上:保存成功能收到消息,ESC 却毫无反应。
  913. //
  914. // 【修法】两条路一起上,互为兜底,且**绝不可能重复触发**:
  915. // ① 回答 WM_GETDLGCODE = DLGC_WANTALLKEYS —— 这是对话框管理器
  916. // 官方的“逃生口”:按 Raymond Chen 原文,只要焦点控件在
  917. // WM_GETDLGCODE 里声明 DLGC_WANTALLKEYS / DLGC_WANTMESSAGE,
  918. // IsDialogMessage 就**不做**上面那套改写,把 WM_KEYDOWN 原样
  919. // DispatchMessage 下来。(顺带把 Tab / 方向键导航还给 DuiLib
  920. // 自己的 PreMessageHandler。)
  921. // ② 万一系统没理会 ①(或焦点落在不声明 DLGCODE 的**原生 EDIT
  922. // 子窗口**上 —— 那时查询对象是那个子窗口而不是本弹框),
  923. // ESC / 回车就会被改写成 WM_COMMAND。那就把
  924. // WM_COMMAND(IDCANCEL / IDOK) 翻译回一条合成的 WM_KEYDOWN,
  925. // 先喂给 pre-filter 链 —— 39 个弹框里既有的分支原样生效,
  926. // 业务代码一行不用改。判据与对话框管理器一致:
  927. // 只在「弹框里没有 ID == IDCANCEL / IDOK 的控件」时才翻译
  928. // (本项目 DLGTEMPLATE 无任何控件,恒成立;有真实控件时放手
  929. // 不管,绝不误伤)。
  930. //
  931. // 【为什么两条路不可能同时生效】同一次按键要么被
  932. // IsDialogMessage 吃掉(走 ②),要么被 DispatchMessage 下来(走 ①)
  933. // —— 互斥,所以不会一次按键关两次弹框。
  934. // -------------------------------------------------------------------------
  935. // [R49d 诊断] 弹框窗口过程实际收到了什么(量极小,只在 ESC/回车/Tab 与
  936. // WM_COMMAND(IDCANCEL/IDOK) 上落笔)—— 用来分辨"被 IsDialogMessage 改写了"
  937. // 还是"原样派发下来了"。
  938. if( uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN )
  939. {
  940. const unsigned nKey = (unsigned)wParam;
  941. if( nKey == VK_ESCAPE || nKey == VK_RETURN || nKey == VK_TAB )
  942. DuiLibDiag::LogKey(_T("[DLGWP] KEYDOWN key=0x%02X hwnd=%p focus=%p"),
  943. nKey, hWnd, ::GetFocus());
  944. }
  945. else if( uMsg == WM_COMMAND )
  946. {
  947. const unsigned nId = (unsigned)LOWORD(wParam);
  948. if( nId == IDCANCEL || nId == IDOK )
  949. DuiLibDiag::LogKey(_T("[DLGWP] COMMAND id=%u hi=0x%04X hwnd=%p"),
  950. nId, (unsigned)HIWORD(wParam), hWnd);
  951. }
  952. if( uMsg == WM_GETDLGCODE )
  953. return DLGC_WANTALLKEYS;
  954. // ---------------------------------------------------------------
  955. // [WM_TOUCH 归属权] 注册"补调原窗口过程"的钩子。
  956. //
  957. // 真模态弹框的窗口类是系统对话框类 #32770,它把"把特殊标记的
  958. // WM_MOUSEMOVE 翻译成 WM_TOUCH"这件事放在 DefDlgProc → DefWindowProc 里做。
  959. // 而 __DialogWndProc 一旦 pThis != NULL 就全部交给 HandleMessage,
  960. // 再也不回落 DefDlgProc ⇒ 弹框永远收不到 WM_TOUCH(与实测吻合)。
  961. //
  962. // 这里登记的原过程是 m_pfnOldDlgWndProc(也就是 DefDlgProc),
  963. // 使 manager 在吞掉触摸提升鼠标消息时能补调它一次,把翻译链接上。
  964. // ---------------------------------------------------------------
  965. // ⚠️ 判据必须是"当前绑定的窗口不是本窗口",不能写 `GetDefProcRelay() == NULL`:
  966. // 真模态弹框复用的是**同一个** manager 对象(从旧窗口 Detach、再 Attach 到弹框),
  967. // 主窗口早已注册过钩子 ⇒ 按 NULL 判断则弹框永远不重新绑定,
  968. // 转发目标会一直指向主窗口的原过程,补调无效甚至喂错窗口。
  969. DuiLib::CPaintManagerUI* pMgr = FindManagerByPaintWindow(hWnd);
  970. if( pMgr != NULL && pMgr->GetDefProcRelayBoundWindow() != hWnd )
  971. {
  972. // ⚠️ DefDlgProc 的签名是 `INT_PTR (CALLBACK*)(...)`,与 WNDPROC
  973. // (`LRESULT (CALLBACK*)(...)`)返回类型不同,三目运算符会类型不匹配。
  974. // 用一个显式分支赋值,避免 C2446。
  975. WNDPROC pfnTarget = NULL;
  976. if( pThis->m_pfnOldDlgWndProc != NULL )
  977. pfnTarget = pThis->m_pfnOldDlgWndProc;
  978. else
  979. pfnTarget = reinterpret_cast<WNDPROC>(::DefDlgProc);
  980. BindRelayTarget(pfnTarget, hWnd);
  981. pMgr->SetDefProcRelay(&RelayWndProc, hWnd);
  982. }
  983. // [真模态回归修复] 真模态(DialogBoxIndirectParam)期间,系统消息循环不调用主循环的
  984. // TranslateMessage/PreMessageHandler,而本项目大量弹框把"工作线程 PostMessage 的完成消息"
  985. // (如 WM_SHANGPIN_HANDLE_SUCCESS/FAIL、WM_MEMBER_CHECK_*、WM_SHOUKUAN_STATUS_*、
  986. // WM_ZHENGCAN_RENSHU_* 等)挂在 IMessageFilterUI::MessageHandler 上、只经 pre-filter 链分发,
  987. // 因此这些消息在模态下收不到,表现为点保存/确定后卡在"处理中…"。
  988. // 这里在分发 WNDPROC 之前补跑本窗口的 pre-filter 链(pMgr->PreMessageHandler),
  989. // 完全等价于主循环行为(pre-filter 优先,未处理再走 HandleMessage),彻底根除该回归。
  990. // 取不到 manager 时降级为原行为,不引入新回归;WM_TOUCH 等 pre-filter 不处理的消息照常回落。
  991. // ★【R49c】见上方 WM_GETDLGCODE 处的长注释 ②:把对话框管理器
  992. // 改写出来的 WM_COMMAND(IDCANCEL / IDOK) 翻译回一条合成的
  993. // WM_KEYDOWN,先喂 pre-filter 链。两条路互斥,不会重复触发。
  994. if( uMsg == WM_COMMAND && pMgr != NULL )
  995. {
  996. const WORD wId = LOWORD(wParam);
  997. if( ( wId == IDCANCEL || wId == IDOK ) && ::GetDlgItem(hWnd, wId) == NULL )
  998. {
  999. const WPARAM wKey = ( wId == IDCANCEL ) ? (WPARAM)VK_ESCAPE : (WPARAM)VK_RETURN;
  1000. LRESULT lPreKey = 0;
  1001. if( pMgr->PreMessageHandler(WM_KEYDOWN, wKey, lParam, lPreKey) )
  1002. return lPreKey;
  1003. }
  1004. }
  1005. if (pMgr != NULL)
  1006. {
  1007. LRESULT lPre = 0;
  1008. if (pMgr->PreMessageHandler(uMsg, wParam, lParam, lPre))
  1009. return lPre;
  1010. }
  1011. return pThis->HandleMessage(uMsg, wParam, lParam);
  1012. }
  1013. return ::DefDlgProc(hWnd, uMsg, wParam, lParam);
  1014. }
  1015. void CWindowWnd::ShowModalFake()
  1016. {
  1017. ASSERT(::IsWindow(m_hWnd));
  1018. auto p_hwnd = GetWindowOwner(m_hWnd);
  1019. ASSERT(::IsWindow(p_hwnd));
  1020. ASSERT(p_hwnd);
  1021. ::EnableWindow(p_hwnd, FALSE);
  1022. ShowWindow();
  1023. m_bFakeModal = true;
  1024. }
  1025. void CWindowWnd::Close(UINT nRet)
  1026. {
  1027. if (m_bFakeModal)
  1028. {
  1029. auto parent_hwnd = GetWindowOwner(m_hWnd);
  1030. ASSERT(::IsWindow(parent_hwnd));
  1031. ::EnableWindow(parent_hwnd, TRUE);
  1032. ::SetFocus(parent_hwnd);
  1033. m_bFakeModal = false;
  1034. }
  1035. ASSERT(::IsWindow(m_hWnd));
  1036. if (!::IsWindow(m_hWnd)) return;
  1037. // -------------------------------------------------------------------------
  1038. // 真模态:走 EndDialog 结束系统对话框循环。
  1039. //
  1040. // 旧的假模态是 PostMessage(WM_CLOSE) 让自写循环退出;真模态下必须用 EndDialog,
  1041. // 否则 DialogBoxIndirectParam 不会返回。返回值写入 m_nModalRet,由 ShowModal 读走。
  1042. //
  1043. // 注意:EndDialog 会销毁对话框窗口 → 触发 WM_NCDESTROY → __DialogWndProc 收尾
  1044. // (还原窗口过程、清 USERDATA、调 OnFinalMessage)。所以这里不要再 DestroyWindow。
  1045. // -------------------------------------------------------------------------
  1046. if (m_bDoModal)
  1047. {
  1048. m_nModalRet = nRet;
  1049. // ---------------------------------------------------------------------
  1050. ::EndDialog(m_hWnd, (INT_PTR)nRet);
  1051. return;
  1052. }
  1053. PostMessage(WM_CLOSE, (WPARAM)nRet, 0L);
  1054. }
  1055. void CWindowWnd::CenterWindow()
  1056. {
  1057. ASSERT(::IsWindow(m_hWnd));
  1058. ASSERT((GetWindowStyle(m_hWnd)&WS_CHILD)==0);
  1059. RECT rcDlg = { 0 };
  1060. ::GetWindowRect(m_hWnd, &rcDlg);
  1061. RECT rcArea = { 0 };
  1062. RECT rcCenter = { 0 };
  1063. HWND hWnd=*this;
  1064. HWND hWndParent = ::GetParent(m_hWnd);
  1065. HWND hWndCenter = ::GetWindowOwner(m_hWnd);
  1066. if (hWndCenter!=NULL)
  1067. hWnd=hWndCenter;
  1068. // 处理多显示器模式下屏幕居中
  1069. MONITORINFO oMonitor = {};
  1070. oMonitor.cbSize = sizeof(oMonitor);
  1071. ::GetMonitorInfo(::MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST), &oMonitor);
  1072. rcArea = oMonitor.rcWork;
  1073. if( hWndCenter == NULL )
  1074. rcCenter = rcArea;
  1075. else
  1076. ::GetWindowRect(hWndCenter, &rcCenter);
  1077. int DlgWidth = rcDlg.right - rcDlg.left;
  1078. int DlgHeight = rcDlg.bottom - rcDlg.top;
  1079. // Find dialog's upper left based on rcCenter
  1080. int xLeft = (rcCenter.left + rcCenter.right) / 2 - DlgWidth / 2;
  1081. int yTop = (rcCenter.top + rcCenter.bottom) / 2 - DlgHeight / 2;
  1082. // The dialog is outside the screen, move it inside
  1083. if( xLeft < rcArea.left ) xLeft = rcArea.left;
  1084. else if( xLeft + DlgWidth > rcArea.right ) xLeft = rcArea.right - DlgWidth;
  1085. if( yTop < rcArea.top ) yTop = rcArea.top;
  1086. else if( yTop + DlgHeight > rcArea.bottom ) yTop = rcArea.bottom - DlgHeight;
  1087. ::SetWindowPos(m_hWnd, NULL, xLeft, yTop, -1, -1, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
  1088. }
  1089. void CWindowWnd::SetIcon(UINT nRes)
  1090. {
  1091. HICON hIcon = (HICON)::LoadImage(CPaintManagerUI::GetInstance(), MAKEINTRESOURCE(nRes), IMAGE_ICON,
  1092. (::GetSystemMetrics(SM_CXICON) + 15) & ~15, (::GetSystemMetrics(SM_CYICON) + 15) & ~15, // 防止高DPI下图标模糊
  1093. LR_DEFAULTCOLOR);
  1094. ASSERT(hIcon);
  1095. ::SendMessage(m_hWnd, WM_SETICON, (WPARAM) TRUE, (LPARAM) hIcon);
  1096. hIcon = (HICON)::LoadImage(CPaintManagerUI::GetInstance(), MAKEINTRESOURCE(nRes), IMAGE_ICON,
  1097. (::GetSystemMetrics(SM_CXICON) + 15) & ~15, (::GetSystemMetrics(SM_CYICON) + 15) & ~15, // 防止高DPI下图标模糊
  1098. LR_DEFAULTCOLOR);
  1099. ASSERT(hIcon);
  1100. ::SendMessage(m_hWnd, WM_SETICON, (WPARAM) FALSE, (LPARAM) hIcon);
  1101. }
  1102. bool CWindowWnd::RegisterWindowClass()
  1103. {
  1104. WNDCLASS wc = { 0 };
  1105. wc.style = GetClassStyle();
  1106. wc.cbClsExtra = 0;
  1107. wc.cbWndExtra = 0;
  1108. wc.hIcon = NULL;
  1109. wc.lpfnWndProc = CWindowWnd::__WndProc;
  1110. wc.hInstance = CPaintManagerUI::GetInstance();
  1111. wc.hCursor = ::LoadCursor(NULL, IDC_ARROW);
  1112. wc.hbrBackground = NULL;
  1113. wc.lpszMenuName = NULL;
  1114. wc.lpszClassName = GetWindowClassName();
  1115. ATOM ret = ::RegisterClass(&wc);
  1116. ASSERT(ret!=NULL || ::GetLastError()==ERROR_CLASS_ALREADY_EXISTS);
  1117. return ret != NULL || ::GetLastError() == ERROR_CLASS_ALREADY_EXISTS;
  1118. }
  1119. bool CWindowWnd::RegisterSuperclass()
  1120. {
  1121. if(m_bUnicode) {
  1122. #ifndef UNICODE
  1123. LPWSTR lpSuperClassName = a2w((char*)GetSuperClassName());
  1124. LPWSTR lpClassName = a2w((char*)GetWindowClassName());
  1125. #else
  1126. LPWSTR lpSuperClassName = (LPWSTR)GetSuperClassName();
  1127. LPWSTR lpClassName = (LPWSTR)GetWindowClassName();
  1128. #endif
  1129. // Get the class information from an existing
  1130. // window so we can subclass it later on...
  1131. WNDCLASSEXW wc = { 0 };
  1132. wc.cbSize = sizeof(WNDCLASSEXW);
  1133. if( !::GetClassInfoExW(NULL, lpSuperClassName, &wc) ) {
  1134. if( !::GetClassInfoExW(CPaintManagerUI::GetInstance(), lpSuperClassName, &wc) ) {
  1135. ASSERT(!"Unable to locate window class");
  1136. return NULL;
  1137. }
  1138. }
  1139. m_OldWndProc = wc.lpfnWndProc;
  1140. wc.lpfnWndProc = CWindowWnd::__ControlProc;
  1141. wc.hInstance = CPaintManagerUI::GetInstance();
  1142. wc.lpszClassName = lpClassName;
  1143. ATOM ret = ::RegisterClassExW(&wc);
  1144. #ifndef UNICODE
  1145. delete []lpClassName;
  1146. delete []lpSuperClassName;
  1147. #endif
  1148. ASSERT(ret!=NULL || ::GetLastError()==ERROR_CLASS_ALREADY_EXISTS);
  1149. return ret != NULL || ::GetLastError() == ERROR_CLASS_ALREADY_EXISTS;
  1150. }
  1151. else {
  1152. // Get the class information from an existing
  1153. // window so we can subclass it later on...
  1154. WNDCLASSEX wc = { 0 };
  1155. wc.cbSize = sizeof(WNDCLASSEX);
  1156. if( !::GetClassInfoEx(NULL, GetSuperClassName(), &wc) ) {
  1157. if( !::GetClassInfoEx(CPaintManagerUI::GetInstance(), GetSuperClassName(), &wc) ) {
  1158. ASSERT(!"Unable to locate window class");
  1159. return NULL;
  1160. }
  1161. }
  1162. m_OldWndProc = wc.lpfnWndProc;
  1163. wc.lpfnWndProc = CWindowWnd::__ControlProc;
  1164. wc.hInstance = CPaintManagerUI::GetInstance();
  1165. wc.lpszClassName = GetWindowClassName();
  1166. ATOM ret = ::RegisterClassEx(&wc);
  1167. ASSERT(ret!=NULL || ::GetLastError()==ERROR_CLASS_ALREADY_EXISTS);
  1168. return ret != NULL || ::GetLastError() == ERROR_CLASS_ALREADY_EXISTS;
  1169. }
  1170. }
  1171. // 注:RelayWndProc / BindRelayTarget 已上移到文件前部的**匿名 namespace 内**
  1172. // (约 308 / 317 行),因为 __DialogWndProc 比本函数先出现、也要用到它们。
  1173. // 另注:FindManagerByPaintWindow 也已移到匿名 namespace **之外**(约 350 行),
  1174. // 以便与 UIBase.h 里的同名声明一一对应(否则调用点会报 C2668 二义)。
  1175. LRESULT CALLBACK CWindowWnd::__WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
  1176. { CWindowWnd* pThis = NULL;
  1177. if( uMsg == WM_NCCREATE ) {
  1178. LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam);
  1179. pThis = static_cast<CWindowWnd*>(lpcs->lpCreateParams);
  1180. pThis->m_hWnd = hWnd;
  1181. ::SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LPARAM>(pThis));
  1182. }
  1183. else {
  1184. pThis = reinterpret_cast<CWindowWnd*>(::GetWindowLongPtr(hWnd, GWLP_USERDATA));
  1185. if( uMsg == WM_NCDESTROY && pThis != NULL ) {
  1186. LRESULT lRes = ::CallWindowProc(pThis->m_OldWndProc, hWnd, uMsg, wParam, lParam);
  1187. // [WM_TOUCH 归属权] 解绑转发目标,避免悬垂到已销毁窗口的窗口过程。
  1188. DuiLib::CPaintManagerUI* pMgrNc = FindManagerByPaintWindow(hWnd);
  1189. if( pMgrNc != NULL ) pMgrNc->ClearDefProcRelay(hWnd);
  1190. BindRelayTarget(NULL, NULL);
  1191. // ⚠️【第 45 轮】这里必须用实参 hWnd,不能写 pThis->m_hWnd:
  1192. // 真模态路径下 ShowModal 会**先把 m_hWnd 置空**再去销毁窗口(见那里的长注释),
  1193. // 此时 pThis->m_hWnd == NULL,用它去 SetWindowLongPtr 等于什么都没做
  1194. // ⇒ 该 HWND 的 GWLP_USERDATA 会留着已死的 this,直到句柄被系统复用。
  1195. ::SetWindowLongPtr(hWnd, GWLP_USERDATA, 0L);
  1196. if( pThis->m_bSubclassed ) pThis->Unsubclass();
  1197. pThis->m_hWnd = NULL;
  1198. pThis->OnFinalMessage(hWnd);
  1199. return lRes;
  1200. }
  1201. }
  1202. if( pThis != NULL ) {
  1203. // ---------------------------------------------------------------
  1204. // [WM_TOUCH 归属权] 注册"补调原窗口过程"的钩子。
  1205. // 把 m_OldWndProc(本窗口类的原过程)登记为转发目标,并交给 manager。
  1206. // manager 在吞掉"触摸提升 WM_MOUSEMOVE"时会回调它一次,
  1207. // 让系统的 WM_TOUCH 翻译链不断掉。
  1208. //
  1209. // 判据用"绑定窗口 != 本窗口"而非"钩子为空":真模态弹框复用同一个
  1210. // manager,换 HWND 时必须能重新绑定到对话框架自己的原过程。
  1211. // 开销:仅在窗口首个消息时走一遍查找,之后靠 manager 内的指针直连。
  1212. // ---------------------------------------------------------------
  1213. DuiLib::CPaintManagerUI* pMgr = FindManagerByPaintWindow(hWnd);
  1214. if( pMgr != NULL && pMgr->GetDefProcRelayBoundWindow() != hWnd )
  1215. {
  1216. BindRelayTarget(pThis->m_OldWndProc, hWnd);
  1217. pMgr->SetDefProcRelay(&RelayWndProc, hWnd);
  1218. }
  1219. return pThis->HandleMessage(uMsg, wParam, lParam);
  1220. }
  1221. else {
  1222. return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
  1223. }
  1224. }
  1225. LRESULT CALLBACK CWindowWnd::__ControlProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
  1226. {
  1227. CWindowWnd* pThis = NULL;
  1228. if( uMsg == WM_NCCREATE ) {
  1229. LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam);
  1230. pThis = static_cast<CWindowWnd*>(lpcs->lpCreateParams);
  1231. ::SetProp(hWnd, _T("WndX"), (HANDLE) pThis);
  1232. pThis->m_hWnd = hWnd;
  1233. }
  1234. else {
  1235. pThis = reinterpret_cast<CWindowWnd*>(::GetProp(hWnd, _T("WndX")));
  1236. if( uMsg == WM_NCDESTROY && pThis != NULL ) {
  1237. LRESULT lRes = ::CallWindowProc(pThis->m_OldWndProc, hWnd, uMsg, wParam, lParam);
  1238. if( pThis->m_bSubclassed ) pThis->Unsubclass();
  1239. ::SetProp(hWnd, _T("WndX"), NULL);
  1240. pThis->m_hWnd = NULL;
  1241. pThis->OnFinalMessage(hWnd);
  1242. return lRes;
  1243. }
  1244. }
  1245. if( pThis != NULL ) {
  1246. return pThis->HandleMessage(uMsg, wParam, lParam);
  1247. }
  1248. else {
  1249. return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
  1250. }
  1251. }
  1252. LRESULT CWindowWnd::SendMessage(UINT uMsg, WPARAM wParam /*= 0*/, LPARAM lParam /*= 0*/)
  1253. {
  1254. ASSERT(::IsWindow(m_hWnd));
  1255. return ::SendMessage(m_hWnd, uMsg, wParam, lParam);
  1256. }
  1257. LRESULT CWindowWnd::PostMessage(UINT uMsg, WPARAM wParam /*= 0*/, LPARAM lParam /*= 0*/)
  1258. {
  1259. ASSERT(::IsWindow(m_hWnd));
  1260. return ::PostMessage(m_hWnd, uMsg, wParam, lParam);
  1261. }
  1262. void CWindowWnd::ResizeClient(int cx /*= -1*/, int cy /*= -1*/)
  1263. {
  1264. ASSERT(::IsWindow(m_hWnd));
  1265. RECT rc = { 0 };
  1266. if( !::GetClientRect(m_hWnd, &rc) ) return;
  1267. if( cx != -1 ) rc.right = cx;
  1268. if( cy != -1 ) rc.bottom = cy;
  1269. if( !::AdjustWindowRectEx(&rc, GetWindowStyle(m_hWnd), (!(GetWindowStyle(m_hWnd) & WS_CHILD) && (::GetMenu(m_hWnd) != NULL)), GetWindowExStyle(m_hWnd)) ) return;
  1270. ::SetWindowPos(m_hWnd, NULL, 0, 0, rc.right - rc.left, rc.bottom - rc.top, SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE);
  1271. }
  1272. LRESULT CWindowWnd::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
  1273. {
  1274. return ::CallWindowProc(m_OldWndProc, m_hWnd, uMsg, wParam, lParam);
  1275. }
  1276. void CWindowWnd::OnFinalMessage(HWND /*hWnd*/)
  1277. {
  1278. }
  1279. } // namespace DuiLib