UIBase.cpp 74 KB

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