UIBase.cpp 48 KB

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