UIManager.h 55 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102
  1. #ifndef __UIMANAGER_H__
  2. #define __UIMANAGER_H__
  3. #pragma once
  4. // 本头文件原本不含任何 include(符号由 StdAfx.h/UIlib.h 提供),
  5. // 但触屏相关的成员与状态里直接使用了 std::int64_t 与 std::vector,
  6. // 显式补齐以保证在 DuiLib 库工程(_WIN32_WINNT 兜底为 XP)里也能独立通过编译。
  7. #include <cstdint>
  8. #include <vector>
  9. namespace DuiLib {
  10. /////////////////////////////////////////////////////////////////////////////////////
  11. //
  12. class CControlUI;
  13. class CContainerUI;
  14. class CRichEditUI;
  15. class CIDropTarget;
  16. /////////////////////////////////////////////////////////////////////////////////////
  17. //
  18. enum UILIB_RESTYPE
  19. {
  20. UILIB_FILE=1, // 来自磁盘文件
  21. UILIB_ZIP, // 来自磁盘zip压缩包
  22. UILIB_RESOURCE, // 来自资源
  23. UILIB_ZIPRESOURCE, // 来自资源的zip压缩包
  24. };
  25. /////////////////////////////////////////////////////////////////////////////////////
  26. //
  27. typedef enum EVENTTYPE_UI
  28. {
  29. UIEVENT__FIRST = 1,
  30. UIEVENT__KEYBEGIN,
  31. UIEVENT_KEYDOWN,
  32. UIEVENT_KEYUP,
  33. UIEVENT_CHAR,
  34. UIEVENT_SYSKEY,
  35. UIEVENT__KEYEND,
  36. UIEVENT__MOUSEBEGIN,
  37. UIEVENT_MOUSEMOVE,
  38. UIEVENT_MOUSELEAVE,
  39. UIEVENT_MOUSEENTER,
  40. UIEVENT_MOUSEHOVER,
  41. UIEVENT_BUTTONDOWN,
  42. UIEVENT_BUTTONUP,
  43. UIEVENT_RBUTTONDOWN,
  44. UIEVENT_RBUTTONUP,
  45. UIEVENT_MBUTTONDOWN,
  46. UIEVENT_MBUTTONUP,
  47. UIEVENT_DBLCLICK,
  48. UIEVENT_CONTEXTMENU,
  49. UIEVENT_SCROLLWHEEL,
  50. UIEVENT__MOUSEEND,
  51. // 触摸事件(NIM Duilib 模型)。必须排在 __MOUSEEND 之后:
  52. // CContainerUI::DoEvent 开头对 UIEVENT__MOUSEBEGIN~__MOUSEEND 区间做了 IsMouseEnabled 早退分支,
  53. // 排在区间之外才能保证触摸事件始终走正常派发与冒泡。
  54. UIEVENT__TOUCHBEGIN,
  55. UIEVENT_TOUCHDOWN,
  56. UIEVENT_TOUCHMOVE,
  57. UIEVENT_TOUCHUP,
  58. UIEVENT__TOUCHEND,
  59. UIEVENT_KILLFOCUS,
  60. UIEVENT_SETFOCUS,
  61. UIEVENT_WINDOWSIZE,
  62. UIEVENT_SETCURSOR,
  63. UIEVENT_TIMER,
  64. UIEVENT__LAST,
  65. };
  66. /////////////////////////////////////////////////////////////////////////////////////
  67. //
  68. // 内部保留的消息
  69. typedef enum MSGTYPE_UI
  70. {
  71. UIMSG_TRAYICON = WM_USER + 1,// 托盘消息
  72. UIMSG_SET_DPI, // DPI
  73. WM_MENUCLICK, // 菜单消息
  74. UIMSG_USER = WM_USER + 100, // 程序自定义消息
  75. };
  76. /////////////////////////////////////////////////////////////////////////////////////
  77. //
  78. // Flags for CControlUI::GetControlFlags()
  79. #define UIFLAG_TABSTOP 0x00000001
  80. #define UIFLAG_SETCURSOR 0x00000002
  81. #define UIFLAG_WANTRETURN 0x00000004
  82. // Flags for FindControl()
  83. #define UIFIND_ALL 0x00000000
  84. #define UIFIND_VISIBLE 0x00000001
  85. #define UIFIND_ENABLED 0x00000002
  86. #define UIFIND_HITTEST 0x00000004
  87. #define UIFIND_UPDATETEST 0x00000008
  88. #define UIFIND_TOP_FIRST 0x00000010
  89. #define UIFIND_ME_FIRST 0x80000000
  90. // Flags used for controlling the paint
  91. #define UISTATE_FOCUSED 0x00000001
  92. #define UISTATE_SELECTED 0x00000002
  93. #define UISTATE_DISABLED 0x00000004
  94. #define UISTATE_HOT 0x00000008
  95. #define UISTATE_PUSHED 0x00000010
  96. #define UISTATE_READONLY 0x00000020
  97. #define UISTATE_CAPTURED 0x00000040
  98. /////////////////////////////////////////////////////////////////////////////////////
  99. //
  100. typedef struct UILIB_API tagTFontInfo
  101. {
  102. HFONT hFont;
  103. CDuiString sFontName;
  104. int iSize;
  105. bool bBold;
  106. bool bUnderline;
  107. bool bItalic;
  108. bool bStrikeout;
  109. TEXTMETRIC tm;
  110. } TFontInfo;
  111. typedef struct UILIB_API tagTImageInfo
  112. {
  113. tagTImageInfo();
  114. Gdiplus::Image* pImage;
  115. HBITMAP hBitmap;
  116. LPBYTE pBits;
  117. LPBYTE pSrcBits;
  118. int nX;
  119. int nY;
  120. bool bAlpha;
  121. bool bUseHSL;
  122. CDuiString sResType;
  123. DWORD dwMask;
  124. } TImageInfo;
  125. typedef struct UILIB_API tagTDrawInfo
  126. {
  127. tagTDrawInfo();
  128. void Parse(LPCTSTR pStrImage, LPCTSTR pStrModify, CPaintManagerUI *pManager);
  129. void Clear();
  130. CDuiString sDrawString;
  131. CDuiString sDrawModify;
  132. CDuiString sImageName;
  133. CDuiString sResType;
  134. RECT rcDest;
  135. RECT rcSource;
  136. RECT rcCorner;
  137. DWORD dwMask;
  138. UINT uFade;
  139. UINT uRotate;
  140. bool bHole;
  141. bool bTiledX;
  142. bool bTiledY;
  143. bool bHSL;
  144. bool bGdiplus;
  145. CDuiSize szImage;
  146. RECT rcPadding;
  147. CDuiString sAlign;
  148. } TDrawInfo;
  149. typedef struct UILIB_API tagTPercentInfo
  150. {
  151. double left;
  152. double top;
  153. double right;
  154. double bottom;
  155. } TPercentInfo;
  156. typedef struct UILIB_API tagTResInfo
  157. {
  158. DWORD m_dwDefaultDisabledColor;
  159. DWORD m_dwDefaultFontColor;
  160. DWORD m_dwDefaultLinkFontColor;
  161. DWORD m_dwDefaultLinkHoverFontColor;
  162. DWORD m_dwDefaultSelectedBkColor;
  163. TFontInfo m_DefaultFontInfo;
  164. CStdStringPtrMap m_CustomFonts;
  165. CStdStringPtrMap m_ImageHash;
  166. CStdStringPtrMap m_AttrHash;
  167. CStdStringPtrMap m_StyleHash;
  168. CStdStringPtrMap m_DrawInfoHash;
  169. } TResInfo;
  170. // Structure for notifications from the system
  171. // to the control implementation.
  172. typedef struct UILIB_API tagTEventUI
  173. {
  174. int Type;
  175. CControlUI* pSender;
  176. DWORD dwTimestamp;
  177. POINT ptMouse;
  178. TCHAR chKey;
  179. WORD wKeyState;
  180. WPARAM wParam;
  181. LPARAM lParam;
  182. } TEventUI;
  183. // Drag&Drop control
  184. const TCHAR* const CF_MOVECONTROL = _T("CF_MOVECONTROL");
  185. typedef struct UILIB_API tagTCFMoveUI
  186. {
  187. CControlUI* pControl;
  188. } TCFMoveUI;
  189. // Listener interface
  190. class INotifyUI
  191. {
  192. public:
  193. virtual void Notify(TNotifyUI& msg) = 0;
  194. };
  195. // MessageFilter interface
  196. class IMessageFilterUI
  197. {
  198. public:
  199. virtual LRESULT MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, bool& bHandled) = 0;
  200. };
  201. class ITranslateAccelerator
  202. {
  203. public:
  204. virtual LRESULT TranslateAccelerator(MSG *pMsg) = 0;
  205. };
  206. class IDragDropUI
  207. {
  208. public:
  209. virtual bool OnDragDrop(CControlUI* pControl) { return false; }
  210. };
  211. /////////////////////////////////////////////////////////////////////////////////////
  212. //
  213. typedef CControlUI* (*LPCREATECONTROL)(LPCTSTR pstrType);
  214. class UILIB_API CPaintManagerUI : public CIDropTarget
  215. {
  216. public:
  217. CPaintManagerUI();
  218. ~CPaintManagerUI();
  219. public:
  220. void Init(HWND hWnd, LPCTSTR pstrName = NULL);
  221. // ------------------------------------------------------------------
  222. // 真模态(DialogBox)支持:把本 manager 从原 paint window 上摘下来 / 挂到新窗口上。
  223. //
  224. // 背景:Windows 的 DialogBox 机制不允许调用方指定 HWND,只能由系统创建对话框窗口。
  225. // 而调用方是「先 Create 再 ShowModal」,所以 ShowModal 里必须把已经 Init 好的 manager
  226. // 从旧 HWND 迁移到对话框的新 HWND —— 但**不能**重新走 Init(Init 会清空字体/图片/样式
  227. // 等已加载资源,且会重复 Add 到 m_aPreMessages)。因此这里提供一对轻量的挂载/摘除。
  228. //
  229. // DetachPaintWindow:释放旧 DC、从 m_aPreMessages 摘除,但**保留** m_hWndPaint 之外的
  230. // 全部状态(控件树、资源、定时器、字体…)。
  231. // AttachPaintWindow:绑定新 HWND 与 DC,重新加入 m_aPreMessages。
  232. // ------------------------------------------------------------------
  233. void DetachPaintWindow();
  234. void AttachPaintWindow(HWND hWnd);
  235. // 【R50】真模态弹框:把"首次布局"与"需要重新布局"两个标志重新武装一次。
  236. //
  237. // 背景:调用方是「Create(壳窗口) → manager.Init(壳窗口) → ShowModal()」。真模态把
  238. // 同一个 manager 从壳窗口 Detach、再 Attach 到系统新建的 #32770 对话框上。
  239. // 而 AttachDialog(在壳窗口阶段跑的)把 m_bFirstLayout / m_bUpdateNeeded 置了 true,
  240. // 这两个标志在壳窗口那边就可能已被消费,且**被消费后不会自动复位** ⇒ 弹框这个
  241. // **全新 HWND** 上既不会重新布局,也不会再发 DUI_MSGTYPE_WINDOWINIT ⇒ 业务层挂在
  242. // windowinit 上的初始化(例如"默认把焦点放到商品名输入框")永远不执行。
  243. //
  244. // 为什么这不算硬塞:对一个刚创建、客户区尺寸刚被 SetWindowPos 校正过的**新 HWND**
  245. // 来说,"重新布局一次 + 发一次首次布局完成通知"本来就是正确语义 —— 与 AttachDialog
  246. // 之后新窗口得到的待遇一致。
  247. // ⚠️ 两个标志必须**一起**置:WINDOWINIT 只在 WM_PAINT 的 m_bUpdateNeeded 分支里发,
  248. // 只置 m_bFirstLayout 是发不出来的。
  249. //
  250. // 幂等:两者都只在 WM_PAINT 里读一次、随即置 false ⇒ 最多多补一次通知。
  251. void RewindForModalPaint();
  252. bool IsUpdateNeeded() const;
  253. void NeedUpdate();
  254. void Invalidate();
  255. void Invalidate(RECT& rcItem);
  256. LPCTSTR GetName() const;
  257. HDC GetPaintDC() const;
  258. HWND GetPaintWindow() const;
  259. HWND GetTooltipWindow() const;
  260. int GetHoverTime() const;
  261. void SetHoverTime(int iTime);
  262. POINT GetMousePos() const;
  263. SIZE GetClientSize() const;
  264. SIZE GetInitSize();
  265. void SetInitSize(int cx, int cy);
  266. RECT GetSizeBox();
  267. void SetSizeBox(RECT& rcSizeBox);
  268. RECT GetCaptionRect();
  269. void SetCaptionRect(RECT& rcCaption);
  270. SIZE GetRoundCorner();
  271. // 【2026-09-24】阴影取样用:与 GetRoundCorner() 的区别是后者在蒙版模式下
  272. // 返回 {0,0}(让 OnSize 的 HRGN 硬裁退场),而阴影的形状来源也是 window region
  273. // —— 拿不到 region 时阴影内轮廓会退化成直角,圆角窗口四角露出背景方角缺口。
  274. SIZE GetRoundCornerForShadow();
  275. void SetRoundCorner(int cx, int cy);
  276. // 【最大化铺满整屏】临时抑制圆角(对外表现为"半径 0")。
  277. // 场景:主窗口最大化后要铺满整屏(含任务栏)、四角直角。但皮肤 main.xml 里
  278. // roundcorner="10,10" 会让窗口走「分层 + 内存圆角蒙版」把四角削圆 —— 铺满全屏时
  279. // 屏幕四角就会露出底下的桌面。最大化期间调本接口置 true,还原时置 false。
  280. // 注意:只抑制"形状"(分层蒙版半径 + OnSize 的 HRGN 分支),
  281. // 不动 m_szRoundCorner 配置值,所以还原后圆角照旧。
  282. void SetRoundCornerSuppressed(bool bSuppress);
  283. bool IsRoundCornerSuppressed() const;
  284. SIZE GetMinInfo();
  285. void SetMinInfo(int cx, int cy);
  286. SIZE GetMaxInfo();
  287. void SetMaxInfo(int cx, int cy);
  288. bool IsShowUpdateRect();
  289. void SetShowUpdateRect(bool show);
  290. bool IsNoActivate();
  291. void SetNoActivate(bool bNoActivate);
  292. BYTE GetOpacity() const;
  293. void SetOpacity(BYTE nOpacity);
  294. bool IsLayered();
  295. void SetLayered(bool bLayered);
  296. // 【R37】解除 SetLayeredWindowAttributes 留下的「重定向」模式:清掉再重设
  297. // WS_EX_LAYERED 样式位(MSDN 指定的唯一合法解除方式)。位图分层路径
  298. // (roundcorner)要调 UpdateLayeredWindow(ULW_ALPHA),不解除会 100% 失败。
  299. void RefreshLayeredStyleBit();
  300. RECT& GetLayeredInset();
  301. void SetLayeredInset(RECT& rcLayeredInset);
  302. BYTE GetLayeredOpacity();
  303. void SetLayeredOpacity(BYTE nOpacity);
  304. LPCTSTR GetLayeredImage();
  305. void SetLayeredImage(LPCTSTR pstrImage);
  306. // 【全局总开关】ZP_LAYERED_ROUND —— **默认开启**:roundcorner>0 的窗口自动走
  307. // 「分层渲染 + 内存圆角蒙版」(圆角逐像素平滑)。回退方式(二选一,重启生效):
  308. // ① 环境变量 ZP_LAYERED_ROUND=0;② exe 同目录放空文件 zp_layered_round.off。
  309. // 历史:2026-09-23 因「弹框所有按钮点不动」一度改为 opt-in;根因已修复后恢复默认开启。
  310. //
  311. // 【分层渲染熔断】蒙版不可用时调用:永久关闭**本窗口**的分层渲染并回到普通 GDI 路径。
  312. // 2026-09-01 分层方案翻车(PNG 蒙版没加载 → 蒙版全 0 → 整窗全透明 →「启动什么都看不到」)
  313. // 的根治手段之一:任何拿不到有效蒙版的场合,宁可退回直角,也绝不渲染全透明窗口。
  314. // 只影响调用者自己的 manager,其它窗口(含主窗口)不受牵连。
  315. void DisableLayeredRender();
  316. CShadowUI* GetShadow();
  317. // 主动刷新窗口阴影:按当前 m_hWndPaint 的几何重画阴影位图。
  318. // 供真模态(ShowModal / DialogBoxIndirectParam)在窗口迁移与尺寸校正之后调用 ——
  319. // 见 UIBase.cpp 的 CWindowWnd::__DialogProc。CShadowUI::Update() 是 protected
  320. // 且 CPaintManagerUI 是它的 friend,所以只能从这里转发。
  321. void UpdateShadow();
  322. // [2026-09-26 R31] 分层内存圆角蒙版模式下,父窗「内容 × 蒙版」的预乘像素位图。
  323. // CShadowUI 在圆角弧的 1px 过渡带上用它做精确颜色补偿(消除偏亮的白弧),
  324. // 原理见 UIShadow.cpp 的 R31 段。三个出参可传 NULL:
  325. // pnWidth / pnHeight 位图宽高(= 客户区宽高);
  326. // prcBitsInWndCoords 位图矩形,坐标原点 = GetWindowRect(m_hWndPaint) 的左上角,
  327. // 与 CShadowUI::Update 传给 MakeShadow 的 rcParent 同一坐标系。
  328. // 位图 = 32bpp BI_RGB DIB,行序【自下而上】、行宽 = 位图宽,像素 0xAARRGGBB,
  329. // RGB 已按 alpha 预乘(即 w*c)。
  330. // 非「分层 + 内存圆角蒙版」路径、或本帧尚未绘制(蒙版位图还没建 / 还没调制)时
  331. // 返回 NULL ⇒ 调用方必须回退到旧行为,不要假定它一定可用。
  332. COLORREF* GetLayeredContentBits(int* pnWidth, int* pnHeight, RECT* prcBitsInWndCoords) const;
  333. void SetUseGdiplusText(bool bUse);
  334. bool IsUseGdiplusText() const;
  335. void SetGdiplusTextRenderingHint(int trh);
  336. int GetGdiplusTextRenderingHint() const;
  337. static HINSTANCE GetInstance();
  338. static CDuiString GetInstancePath();
  339. static CDuiString GetCurrentPath();
  340. static HINSTANCE GetResourceDll();
  341. static const CDuiString& GetResourcePath();
  342. static const CDuiString& GetResourceZip();
  343. static const CDuiString& GetResourceZipPwd();
  344. static bool IsCachedResourceZip();
  345. static HANDLE GetResourceZipHandle();
  346. static void SetInstance(HINSTANCE hInst);
  347. static void SetCurrentPath(LPCTSTR pStrPath);
  348. static void SetResourceDll(HINSTANCE hInst);
  349. static void SetResourcePath(LPCTSTR pStrPath);
  350. static void SetResourceZip(LPVOID pVoid, unsigned int len, LPCTSTR password = NULL);
  351. static void SetResourceZip(LPCTSTR pstrZip, bool bCachedResourceZip = false, LPCTSTR password = NULL);
  352. static void SetResourceType(int nType);
  353. static int GetResourceType();
  354. static bool GetHSL(short* H, short* S, short* L);
  355. static void SetHSL(bool bUseHSL, short H, short S, short L); // H:0~360, S:0~200, L:0~200
  356. static void ReloadSkin();
  357. static CPaintManagerUI* GetPaintManager(LPCTSTR pstrName);
  358. static CStdPtrArray* GetPaintManagers();
  359. static bool LoadPlugin(LPCTSTR pstrModuleName);
  360. static CStdPtrArray* GetPlugins();
  361. bool IsForceUseSharedRes() const;
  362. void SetForceUseSharedRes(bool bForce);
  363. // 注意:只支持简单类型指针,因为只释放内存,不会调用类对象的析构函数
  364. void DeletePtr(void* ptr);
  365. DWORD GetDefaultDisabledColor() const;
  366. void SetDefaultDisabledColor(DWORD dwColor, bool bShared = false);
  367. DWORD GetDefaultFontColor() const;
  368. void SetDefaultFontColor(DWORD dwColor, bool bShared = false);
  369. DWORD GetDefaultLinkFontColor() const;
  370. void SetDefaultLinkFontColor(DWORD dwColor, bool bShared = false);
  371. DWORD GetDefaultLinkHoverFontColor() const;
  372. void SetDefaultLinkHoverFontColor(DWORD dwColor, bool bShared = false);
  373. DWORD GetDefaultSelectedBkColor() const;
  374. void SetDefaultSelectedBkColor(DWORD dwColor, bool bShared = false);
  375. TFontInfo* GetDefaultFontInfo();
  376. void SetDefaultFont(LPCTSTR pStrFontName, int nSize, bool bBold, bool bUnderline, bool bItalic, bool bStrikeout, bool bShared = false);
  377. DWORD GetCustomFontCount(bool bShared = false) const;
  378. void AddFontArray(LPCTSTR pstrPath);
  379. HFONT AddFont(int id, LPCTSTR pStrFontName, int nSize, bool bBold, bool bUnderline, bool bItalic, bool bStrikeout, bool bShared = false);
  380. HFONT GetFont(int id);
  381. HFONT GetFont(LPCTSTR pStrFontName, int nSize, bool bBold, bool bUnderline, bool bItalic, bool bStrikeout);
  382. int GetFontIndex(HFONT hFont, bool bShared = false);
  383. int GetFontIndex(LPCTSTR pStrFontName, int nSize, bool bBold, bool bUnderline, bool bItalic, bool bStrikeout, bool bShared = false);
  384. void RemoveFont(HFONT hFont, bool bShared = false);
  385. void RemoveFont(int id, bool bShared = false);
  386. void RemoveAllFonts(bool bShared = false);
  387. TFontInfo* GetFontInfo(int id);
  388. TFontInfo* GetFontInfo(HFONT hFont);
  389. const TImageInfo* GetImage(LPCTSTR bitmap);
  390. const TImageInfo* GetImageEx(LPCTSTR bitmap, LPCTSTR type = NULL, DWORD mask = 0, bool bUseHSL = false, bool bGdiplus = false, HINSTANCE instance = NULL);
  391. const TImageInfo* AddImage(LPCTSTR bitmap, LPCTSTR type = NULL, DWORD mask = 0, bool bUseHSL = false, bool bGdiplus = false, bool bShared = false, HINSTANCE instance = NULL);
  392. const TImageInfo* AddImage(LPCTSTR bitmap, HBITMAP hBitmap, int iWidth, int iHeight, bool bAlpha, bool bShared = false);
  393. void RemoveImage(LPCTSTR bitmap, bool bShared = false);
  394. void RemoveAllImages(bool bShared = false);
  395. static void ReloadSharedImages();
  396. void ReloadImages();
  397. const TDrawInfo* GetDrawInfo(LPCTSTR pStrImage, LPCTSTR pStrModify);
  398. void RemoveDrawInfo(LPCTSTR pStrImage, LPCTSTR pStrModify);
  399. void RemoveAllDrawInfos();
  400. void AddDefaultAttributeList(LPCTSTR pStrControlName, LPCTSTR pStrControlAttrList, bool bShared = false);
  401. LPCTSTR GetDefaultAttributeList(LPCTSTR pStrControlName) const;
  402. bool RemoveDefaultAttributeList(LPCTSTR pStrControlName, bool bShared = false);
  403. void RemoveAllDefaultAttributeList(bool bShared = false);
  404. void AddWindowCustomAttribute(LPCTSTR pstrName, LPCTSTR pstrAttr);
  405. LPCTSTR GetWindowCustomAttribute(LPCTSTR pstrName) const;
  406. bool RemoveWindowCustomAttribute(LPCTSTR pstrName);
  407. void RemoveAllWindowCustomAttribute();
  408. // 样式管理
  409. void AddStyle(LPCTSTR pName, LPCTSTR pStyle, bool bShared = false);
  410. LPCTSTR GetStyle(LPCTSTR pName) const;
  411. BOOL RemoveStyle(LPCTSTR pName, bool bShared = false);
  412. const CStdStringPtrMap& GetStyles(bool bShared = false) const;
  413. void RemoveAllStyle(bool bShared = false);
  414. const TImageInfo* GetImageString(LPCTSTR pStrImage, LPCTSTR pStrModify = NULL);
  415. // 初始化拖拽
  416. bool EnableDragDrop(bool bEnable);
  417. void SetDragDrop(IDragDropUI* pDragDrop);
  418. virtual bool OnDrop(FORMATETC* pFmtEtc, STGMEDIUM& medium,DWORD *pdwEffect);
  419. bool IsValid();
  420. bool AttachDialog(CControlUI* pControl);
  421. bool InitControls(CControlUI* pControl, CControlUI* pParent = NULL);
  422. void ReapObjects(CControlUI* pControl);
  423. bool AddOptionGroup(LPCTSTR pStrGroupName, CControlUI* pControl);
  424. CStdPtrArray* GetOptionGroup(LPCTSTR pStrGroupName);
  425. void RemoveOptionGroup(LPCTSTR pStrGroupName, CControlUI* pControl);
  426. void RemoveAllOptionGroups();
  427. CControlUI* GetFocus() const;
  428. void SetFocus(CControlUI* pControl);
  429. void SetFocusNeeded(CControlUI* pControl);
  430. bool SetNextTabControl(bool bForward = true);
  431. bool SetTimer(CControlUI* pControl, UINT nTimerID, UINT uElapse);
  432. bool KillTimer(CControlUI* pControl, UINT nTimerID);
  433. void KillTimer(CControlUI* pControl);
  434. void RemoveAllTimers();
  435. void SetCapture();
  436. void ReleaseCapture();
  437. bool IsCaptured();
  438. bool IsPainting();
  439. void SetPainting(bool bIsPainting);
  440. bool AddNotifier(INotifyUI* pControl);
  441. bool RemoveNotifier(INotifyUI* pControl);
  442. void SendNotify(TNotifyUI& Msg, bool bAsync = false);
  443. void SendNotify(CControlUI* pControl, LPCTSTR pstrMessage, WPARAM wParam = 0, LPARAM lParam = 0, bool bAsync = false);
  444. bool AddPreMessageFilter(IMessageFilterUI* pFilter);
  445. bool RemovePreMessageFilter(IMessageFilterUI* pFilter);
  446. bool AddMessageFilter(IMessageFilterUI* pFilter);
  447. bool RemoveMessageFilter(IMessageFilterUI* pFilter);
  448. int GetPostPaintCount() const;
  449. bool IsPostPaint(CControlUI* pControl);
  450. bool AddPostPaint(CControlUI* pControl);
  451. bool RemovePostPaint(CControlUI* pControl);
  452. bool SetPostPaintIndex(CControlUI* pControl, int iIndex);
  453. int GetNativeWindowCount() const;
  454. RECT GetNativeWindowRect(HWND hChildWnd);
  455. bool AddNativeWindow(CControlUI* pControl, HWND hChildWnd);
  456. bool RemoveNativeWindow(HWND hChildWnd);
  457. void AddDelayedCleanup(CControlUI* pControl);
  458. void AddMouseLeaveNeeded(CControlUI* pControl);
  459. bool RemoveMouseLeaveNeeded(CControlUI* pControl);
  460. bool AddTranslateAccelerator(ITranslateAccelerator *pTranslateAccelerator);
  461. bool RemoveTranslateAccelerator(ITranslateAccelerator *pTranslateAccelerator);
  462. bool TranslateAccelerator(LPMSG pMsg);
  463. CControlUI* GetRoot() const;
  464. CControlUI* FindControl(POINT pt) const;
  465. CControlUI* FindControl(LPCTSTR pstrName) const;
  466. CControlUI* FindSubControlByPoint(CControlUI* pParent, POINT pt) const;
  467. CControlUI* FindSubControlByName(CControlUI* pParent, LPCTSTR pstrName) const;
  468. CControlUI* FindSubControlByClass(CControlUI* pParent, LPCTSTR pstrClass, int iIndex = 0);
  469. CStdPtrArray* FindSubControlsByClass(CControlUI* pParent, LPCTSTR pstrClass);
  470. static void MessageLoop();
  471. static bool TranslateMessage(const LPMSG pMsg);
  472. static void Term();
  473. CDPI* GetDPIObj();
  474. void ResetDPIAssets();
  475. void RebuildFont(TFontInfo* pFontInfo);
  476. void SetDPI(int iDPI);
  477. static void SetAllDPI(int iDPI);
  478. bool MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lRes);
  479. bool PreMessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lRes);
  480. void UsedVirtualWnd(bool bUsed);
  481. // 触屏支持(参考 NIM Duilib 模型,库级统一接管:主窗口 + 所有弹框通用)。
  482. //
  483. // 【重要】滑动逻辑与输入通道解耦(Begin/Update/End 三个共享函数):
  484. // Win10 上同一次触摸会在**两条互相独立**的通道上产生消息 ——
  485. // WM_TOUCH 与 WM_POINTER*(Win8+ 指针栈)。哪条通道驱动本次手势是运行时决定的
  486. // (本台收银机:主窗口常常 WM_TOUCH 先到;弹框 / 被指针抢走的手势只有指针)。
  487. //
  488. // 早期版本把"滑动"的全部实现(1:1 跟手平移 / 拖滚动条滑块 / Δy→滚轮)只写在
  489. // WM_TOUCH 通道里。于是指针通道一旦抢到手势,就出现两个症状:
  490. // · 「滑不动」—— 没有任何滑动逻辑被执行;
  491. // · 「滑动反倒触发点击」—— 指针把 UP 归一化成 WM_LBUTTONUP 走原生点击路径,
  492. // 而 tap/drag 判定所需的 m_bTouchMoved 从未被置真。
  493. // 现在抽成通道无关的 Begin/Update/End,两条通道都调用同一套 —— 行为必然一致。
  494. //
  495. // 派发给命中控件的事件语义(两通道统一):
  496. // DOWN → UIEVENT_TOUCHDOWN 滚动条据此抓滑块;容器据此记手势起点(其余控件忽略)
  497. // MOVE → UIEVENT_TOUCHMOVE CScrollBarUI 消费则拖滑块;否则冒泡给 CContainerUI 做 1:1 跟手平移
  498. // UP → UIEVENT_TOUCHUP 滚动条据此松开滑块;容器收尾(其余控件忽略)
  499. //
  500. // 按钮"点击"由 **End 的返回值(tap 判定)** 统一决定是否合成 BUTTONDOWN/BUTTONUP:
  501. // 位移超过 8px 阈值 ⇒ 判为拖拽 ⇒ **不**触发点击(这是"滑动误点"的防线);
  502. // 例外:位移虽大但抬起时仍落在同一控件上、且该控件不在可滚动容器内 ⇒ 仍算轻点
  503. // (吸收手指按下时的轻微抖动,否则弹框里的小按钮会点不动)。
  504. //
  505. // 注册在 Init 中完成,无需逐窗口样板。
  506. void InitTouchSupport();
  507. void OnTouchInput(WPARAM wParam, LPARAM lParam);
  508. // ------------------------------------------------------------------
  509. // [手势滑动] 与输入通道无关的"手指拖动"核心逻辑。
  510. //
  511. // 为什么需要把它单独抽出来:WM_TOUCH 与 WM_POINTER* 是两条**互相独立**的通道
  512. // (本台收银机上弹框只拿得到指针通道,主窗口有时也被指针抢先),而"滑动"的全部
  513. // 实现(1:1 跟手平移 / 拖滚动条滑块 / Δy→滚轮)原先只写在 WM_TOUCH 的
  514. // OnTouchInput 里。于是指针通道一旦抢到本次手势:
  515. // · WM_TOUCH 整条被 OnTouchInput 忽略 ⇒ 没有任何滑动逻辑被执行 ⇒ 「滑不动」;
  516. // · 指针通道把 UP 归一化成 WM_LBUTTONUP 走原生点击路径,
  517. // tap/drag 判定(m_bTouchMoved)从未被触摸链置真 ⇒「滑动反倒触发点击」。
  518. // 现在把这段逻辑抽成通道无关的公共函数,两条通道都调用它,行为立刻一致。
  519. //
  520. // 调用约定(两条通道都必须按这个顺序调):
  521. // BeginSlideGesture(pt) —— DOWN 时调一次:记起点、命中控件、找可滚动容器;
  522. // UpdateSlideGesture(pt) —— MOVE/UPDATE 时调:按 Δ 派发 UIEVENT_TOUCHMOVE(+合成滚轮);
  523. // EndSlideGesture(pt) —— UP 时调:派发 UIEVENT_TOUCHUP 并返回"是否算一次轻点"。
  524. // 详见各函数实现处的长注释。
  525. // ------------------------------------------------------------------
  526. void BeginTouchSlideGesture(POINT pt);
  527. void UpdateTouchSlideGesture(POINT pt);
  528. // 返回 true 表示"本次手势应当按轻点处理"(未越过拖拽阈值)。
  529. bool EndTouchSlideGesture(POINT pt);
  530. // [对标 nim_duilib] WM_POINTER*(Win8+ 指针栈)处理:把指针消息归一化成鼠标消息,
  531. // 与 WM_TOUCH 归一化形成互补通道。返回 true 表示已消费该消息。
  532. // 说明:本项目 _WIN32_WINNT=0x0601,WM_POINTER* 宏在编译期不可见,
  533. // 故 uMsg 用字面量常量比较(见 UIManager.cpp 的 PointerMsg 命名空间)。
  534. bool OnPointerInput(UINT uMsg, WPARAM wParam, LPARAM lParam);
  535. void ClearTouchHoverState(POINT pt);
  536. // [触摸点击 · 兜底] 清一个控件的"按下/悬浮"视觉(PUSHED | CAPTURED | HOT)。
  537. //
  538. // ⚠️ 这是**兜底**,不是主要修复手段 —— 主要修复在控件侧
  539. // (`CDiandanFoodItemUI::DoEvent` 已按 NIM 的顺序改为"先复位、后 SendNotify")。
  540. //
  541. // 只在三种"没人会替我们收尾"的路径上调用(见 TouchUp):
  542. // (a) 手势被判为滑动(不派发点击);
  543. // (b) 轻点但没有候选点击目标;
  544. // (c) 抬手点已被上层窗口(弹框)覆盖(`TOUCHUP ABORT covered-by-overlay`)。
  545. // 另外鼠标路径的 WM_LBUTTONUP 也用它。
  546. //
  547. // ⚠️⚠️ 绝不能在"派发 BUTTONDOWN 之前"调用! ⚠️⚠️
  548. // 曾有一版在 TouchUp 派发前先调它,结果**所有商品卡的按压态都不显示了**。
  549. // 原因:触摸链的 BUTTONDOWN/BUTTONUP 是**同一次调用里背靠背合成**的,
  550. // 中间没有消息循环、没有重绘机会。事前清一次 ⇒ 等于"先擦、再置(pressed)、
  551. // 再复位(normal)"三步连跑,pressed 帧一帧都画不出来。
  552. // 鼠标路径能看见 pressed,是因为它的 DOWN/UP 是**两条独立消息**,中间会重绘。
  553. // ⇒ 正确做法是让**控件自己**在 `UIEVENT_TOUCHDOWN` 时置 pressed
  554. // (按下瞬间就能重绘),抬起时再复位。
  555. //
  556. // 参照 NIM_Duilib:`Control::ButtonUp` 里 `m_uButtonState = kControlStateNormal`
  557. // 出现在 `Activate()`(= 发 CLICK)**之前** —— 复位必须早于业务回调。
  558. // 本函数把这个"复位"独立出来,供兜底路径使用。
  559. //
  560. // ⚠️ 只处理传入的那一个控件(不向上遍历祖先链):
  561. // 会挂按下态的只有被点击的控件本身(BUTTONDOWN 不冒泡),祖先从未被置位。
  562. // pControl 传 NULL 时按 pt 命中查找。
  563. //
  564. // ⚠️ 只清显示位:不发通知、不 Activate。
  565. // ⚠️ 只对"库内按钮"(`DUI_CTR_BUTTON`)生效 —— 自定义控件(如商品卡)
  566. // 不吃 UISTATE_*,复位必须由它自己的 DoEvent 负责。
  567. void ClearPushedStateAt(CControlUI* pControl, POINT pt);
  568. // ------------------------------------------------------------------
  569. // [触摸点击 · 顺序修复的核心] 触摸链的"抬起"处理:收尾 + 按正确顺序派发点击。
  570. //
  571. // ⚠️⚠️ 前提事实(真机日志实测,务必记住,别再假设)⚠️⚠️
  572. // 本机 Win10 的触摸**只产生 WM_POINTER\***,**不会**由系统提升出
  573. // WM_LBUTTONDOWN / WM_LBUTTONUP(`fromtouch=1` 的 LBUTTON 消息数为 **0**)。
  574. // ⇒ 触摸链**必须自己派发**点击事件,绝不能指望"放行原生鼠标消息"。
  575. // (曾经按"放行原生消息"改过一版,结果整机点不动 —— 因为压根没有那条消息。)
  576. //
  577. // 背景(用户真机 Win10 bug · 两个):
  578. // ① 弹框里的按钮会"透传" —— 点弹框的「选好了」,弹框关闭后,
  579. // 屏幕同位置的**主窗口商品卡**也被点了一次;
  580. // ② 所有商品卡的**按压态都不显示**了。
  581. //
  582. // 根因(参照 NIM_Duilib 的真实实现得出):
  583. // ② 曾有一版在派发点击**之前**先 `ClearPushedStateAt`。触摸链的
  584. // BUTTONDOWN/BUTTONUP 是同一次调用背靠背合成的,中间无重绘
  585. // ⇒ "先擦、再置 pressed、再复位 normal"三段连跑,pressed 帧画不出来。
  586. // ① `m_pEventClick` 是 **DOWN 时刻**记的(那时弹框还不存在);
  587. // 抬手时弹框已盖在手指上,但指针仍指向背后的主窗口卡片
  588. // ⇒ 我们把点击派发给了它。鼠标路径没这问题是因为真模态弹框
  589. // (独立 `#32770` HWND + 独立 manager)独占捕获与消息投递。
  590. //
  591. // 修法(顺序修复 + 死线复核,全程无时间戳):
  592. // 0) 复核心脏判据 —— `pClick->GetManager() == this`
  593. // 且 `FindControl(pt)` 沿祖先链仍命中 `pClick`;
  594. // 不满足 ⇒ `TOUCHUP ABORT ...`,只收尾、**绝不 SendNotify**(治 ①)。
  595. // 1) 收掉 hover 登记(`m_pEventHover` + `m_aNeedMouseLeaveNeeded`),
  596. // **不碰按下态**;
  597. // 2) 派发 BUTTONDOWN / BUTTONUP —— 让业务回调(ShowModal)跑。
  598. // 按下态的置/复完全由控件自己的 DoEvent 负责:
  599. // `UIEVENT_TOUCHDOWN` → pressed(按下瞬间就画出来,治 ②)
  600. // `UIEVENT_BUTTONUP` → **先** SetCardState("normal") **再** SendNotify
  601. // (详见 CDiandanFoodItemUI::DoEvent 的注释,与 NIM `Control::ButtonUp`
  602. // 的 `先复位 → Activate()` 同构。)
  603. // 3) 末尾 `ClearPushedStateAt(NULL, pt)` 只对 `DUI_CTR_BUTTON` 窄类兜底。
  604. //
  605. // 全程**无时间戳**:只由"本次手势是否算轻点"决定,无任何超时。
  606. // ------------------------------------------------------------------
  607. bool TouchUp(const POINT& pt, bool bTapLike);
  608. // 记录"跨窗口通用的抑制参考点":把本窗口客户区点换算成屏幕坐标后写进
  609. // **线程级**参考点 `s_ptLastTouchScreen`(定义在 UIManager.cpp 文件头,
  610. // 那里有"为什么不能用 manager 成员"的完整说明)。
  611. // 约定:凡是写了 m_ptLastTouchPos 的**触摸**路径,都应同步调一次本函数 ——
  612. // 二者是同一个物理点的两种坐标表达,缺了屏幕坐标那份,"窗口一换判据就失准"。
  613. void SetLastTouchScreenPoint(POINT ptClient);
  614. // ------------------------------------------------------------------
  615. // [WM_TOUCH 归属权] 未被文档化的关键机制与"补调 DefWindowProc"钩子。
  616. //
  617. // 机制(来源:The Witness 作者逆向结论 + MSDN Architectural Overview):
  618. // `WM_TOUCH` **不会**由系统直接投递。系统先发一条"特殊标记的"
  619. // `WM_MOUSEMOVE`(开了 raw input 时是 `WM_INPUT`);
  620. // **只有这条消息一路走到 `DefWindowProc` 里**,DefWindowProc 才会
  621. // 把驻留在窗口上的 `WM_TOUCH` "翻译"出来并派发。
  622. //
  623. // 推论:任何一层**吞掉**这条被标记的 `WM_MOUSEMOVE` 且不回落到
  624. // `DefWindowProc`,就会**永久掐断该窗口后续 WM_TOUCH 的派发链**。
  625. //
  626. // 本项目的两个真实断点(都在 MessageHandler 的 WM_MOUSEMOVE 分支):
  627. // · `if (bPromotedTouchMove && m_pEventTouch != NULL) { ... return true; }`
  628. // —— 触摸手势进行中,丢弃"提升出来的鼠标移动";
  629. // · `if (s_bSuppressHoverAfterTouch) { ... return true; }`
  630. // —— 触摸抬手后抑制悬浮态复活,丢弃合成移动(该状态是**线程级**的,
  631. // 定义在 UIManager.cpp 文件头;不是本类的成员)。
  632. // 两者都 `return true`,顺着 MessageHandler → HandleMessage 直接返回,
  633. // **跳过了 DefWindowProc** ⇒ 弹框(#32770 对话框类就是靠 DefDlgProc→DefWindowProc
  634. // 这条链来"翻译"WM_TOUCH 的)永远收不到 WM_TOUCH。
  635. // 这与实测完全吻合:主窗口有 `WNDPROC-TOUCH`、弹框零条。
  636. //
  637. // 钩子用法:窗口过程(__WndProc / __DialogWndProc)在构造时把自己的
  638. // "原窗口过程调用入口"注册进来;MessageHandler 在**吞掉那条被标记的
  639. // WM_MOUSEMOVE 之前**调用一次 `RelayDefWindowProc(...)`,
  640. // 让系统的"翻译"链继续跑 —— 我们仍然吃掉消息、不破坏 DuiLib 状态机,
  641. // 只是补一次 DefWindowProc 的副作用。
  642. //
  643. // 默认**关闭**(见 UIManager.cpp 的 m_bRelayDefProcForTouchMove):这是一个
  644. // 尚未在本机验证的机制假设,先做成可开关 + 带日志,验证通过再考虑默认开启。
  645. // ------------------------------------------------------------------
  646. // 钩子签名:就是一个"把消息喂给原窗口过程"的调用入口。
  647. // 参数与普通 WNDPROC 一致;返回值同样是 LRESULT(本场景只关心副作用)。
  648. typedef LRESULT (CALLBACK* PFN_RELAY_DEFPROC)(HWND, UINT, WPARAM, LPARAM);
  649. void SetDefProcRelay(PFN_RELAY_DEFPROC pfnRelay, HWND hWndTarget);
  650. void ClearDefProcRelay(HWND hWndTarget);
  651. PFN_RELAY_DEFPROC GetDefProcRelay() const { return m_pfnDefProcRelay; }
  652. // 当前钩子绑定的窗口。窗口过程用它判断"是否需要为本窗口(重新)绑定"——
  653. // 真模态弹框复用的是同一个 manager 对象,必须靠这个判据在换 HWND 时重新绑定。
  654. HWND GetDefProcRelayBoundWindow() const { return m_hWndDefProcRelay; }
  655. // 让"被吞掉的那条触摸提升鼠标消息"补走一次原窗口过程(仅当开关开启)。
  656. void RelayDefWindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam, LPCTSTR pszWhy);
  657. bool IsDefProcRelayEnabled() const { return m_bRelayDefProcForTouchMove; }
  658. void SetDefProcRelayEnabled(bool bEnable) { m_bRelayDefProcForTouchMove = bEnable; }
  659. // 当前处于模态状态的弹框句柄(由 CWindowWnd::ShowModal 设置/清除)。
  660. // 目的是:万一 Windows 把 WM_TOUCH 错投给了被弹框挡住的 owner(主窗口),
  661. // 非模态窗口一律忽略触摸,避免“弹框还开着、主窗口却在响应手指”的穿透。
  662. static void SetModalWindow(HWND hWnd);
  663. static HWND GetModalWindow();
  664. // ------------------------------------------------------------------
  665. // ★【Win7"咚"声 · 根治】"提升副本在途"标志的只读快照。
  666. //
  667. // 真 ⇒ 触摸通道确实收到了本次手势,它的提升鼠标副本正排在队列里等着被投递。
  668. // 用途:`CWindowWnd::ShowModal` 进模态循环之前用它决定"要不要把队列里那份
  669. // 在途副本摘掉" —— 因为系统对"投给**禁用**窗口的鼠标消息"的默认处理是
  670. // `MessageBeep(0)` + 退回队列,那就是 Win7 上那一声"咚"。
  671. //
  672. // 判据只依赖"触摸通道收到了这次手势"这一件事(与 PROMOTED-DUP 同源、同一处置位),
  673. // 与模态边界 / 代数水位 / extra 指纹全都无关。详见 UIManager.cpp 里
  674. // `IsPromotedMouseDuplicatePending` / `DropQueuedPromotedMouseMessages` 的定义处。
  675. // ------------------------------------------------------------------
  676. static bool IsPromotedMouseDuplicatePending();
  677. // ★【Win7"咚"声 · 根治】把"本次手势在途的提升鼠标副本"从本线程消息队列里
  678. // **就地摘掉**,返回摘掉的条数(0 = 队列里本来就没有)。
  679. //
  680. // 调用点:`CWindowWnd::ShowModal` 进模态循环之前 —— 那一刻副本必然已排在队列里,
  681. // 而"摘除"与"创建弹框"之间没有消息泵 ⇒ 摘除对投递而言是原子的。
  682. // 原理与"为什么这样不会误伤",见 UIManager.cpp 里本函数的定义处。
  683. static int DropQueuedPromotedMouseMessages(HWND hWndTarget);
  684. // ------------------------------------------------------------------
  685. // ★【Win7 · Combo 下拉框"点了不出来"】"刚刚真的投递过一条提升鼠标点击"的一次性受理口。
  686. //
  687. // 真机现象:Win7 上点 Combo 有时"点了没反应",要连点 2~4 下才弹出
  688. // (日志实证:下拉框建好之后 15~46ms 就被 WM_KILLFOCUS 关掉,10 次失败 10 次如此)。
  689. //
  690. // 根因不在下拉框自己:Win7 会把**同一次触摸**提升成鼠标消息,而这份副本是在
  691. // **触摸抬起之后**才投递的。系统对"把鼠标点击投给非前台窗口"的处理是先激活该窗口,
  692. // 而且**激活发生在进入窗口过程之前**(任何 WndProc 里的闸门都拦不住)。
  693. // 于是:owner 被激活 ⇒ 刚 ShowWindow 出来的下拉框收到 WM_KILLFOCUS ⇒ 自己关掉。
  694. //
  695. // 判定分两步(都只看状态,无时间戳),见 UICombo.cpp 的 DecideAfterFocusLoss:
  696. // ① 焦点丢失之后**紧跟着**真的投递了一条提升鼠标点击(本函数);
  697. // ② 该次点击的落点仍在这个 Combo 自己身上。
  698. // ①+② 同时成立 ⇒ 这次焦点丢失是"打开我那一出手势的回声",不是用户点了别处。
  699. //
  700. // 一次性语义:读完即清(调用方每次判定只关心"自上次判定以来")。
  701. //
  702. // ⚠️【2026-09-22 真机日志修正】原来这里写着"Win10 不产生提升副本 ⇒ 恒返回 false
  703. // ⇒ 调用方行为零变化"。**那个前提是错的**:这台 Win10 收银机**确实**会产生提升
  704. // 副本(60 秒日志里 54 对 0x0201/0x0202),只是它们的归属窗口全是主窗口、
  705. // 而非下拉框所在窗口。48 次判定里 47 次返回真 —— 因为它按"自上次判定以来"
  706. // 结算,而每次手势的副本都落在那次手势自己的判定**之后**,于是恒被**下一次**
  707. // 判定收走 ⇒ 这个标志在稳态下**恒为真、不携带任何信息**。
  708. // ⇒ 下拉框的判定**不得**再以它作为必要条件(见 CComboWnd::DecideAfterFocusLoss)。
  709. // ------------------------------------------------------------------
  710. static bool ConsumePromotedMouseMsgJustSeen();
  711. // ------------------------------------------------------------------
  712. // ★【Win10 Combo 下拉框"再点一下收不起来 / item 点一下没反应"· 根治的判据源】
  713. //
  714. // 返回"**最近一次点击类输入**发生在屏幕上的哪个点":
  715. // · 最近一次是**触摸** ⇒ 返回触摸链记录的真实触点(BeginTouchSlideGesture 里
  716. // 由 ClientToScreen 换算的屏幕坐标,两通道共用同一处);
  717. // · 最近一次是**鼠标** ⇒ 返回 `::GetCursorPos()`。
  718. //
  719. // 消费点:`CComboWnd::DecideAfterFocusLoss` —— 它要回答"这一下焦点丢失,是不是
  720. // 用户正在点我这个 owner Combo"。**必须用触摸触点,不能用 GetCursorPos()**:
  721. // 系统的触摸提升才会挪光标,而本项目的 Combo 开关是触摸链**合成**的点击、
  722. // 发生在提升之前 ⇒ 触摸下 GetCursorPos() 读到的是**上一手**的位置。
  723. // 真机实证:`COMBO-INIT begin cursor=768,251` 而本手触点是 (789,153),
  724. // 768,251 正是上一手点在下拉列表里的位置。详见本函数在 UIManager.cpp 的定义处。
  725. //
  726. // 【自限】从未有过触摸(纯鼠标机器)⇒ 恒走 GetCursorPos() ⇒ 与改动前逐字一致。
  727. // ------------------------------------------------------------------
  728. static POINT GetLastInputScreenPoint();
  729. private:
  730. CStdPtrArray* GetFoundControls();
  731. static CControlUI* CALLBACK __FindControlFromNameHash(CControlUI* pThis, LPVOID pData);
  732. static CControlUI* CALLBACK __FindControlFromCount(CControlUI* pThis, LPVOID pData);
  733. static CControlUI* CALLBACK __FindControlFromPoint(CControlUI* pThis, LPVOID pData);
  734. static CControlUI* CALLBACK __FindControlFromTab(CControlUI* pThis, LPVOID pData);
  735. static CControlUI* CALLBACK __FindControlFromShortcut(CControlUI* pThis, LPVOID pData);
  736. static CControlUI* CALLBACK __FindControlFromName(CControlUI* pThis, LPVOID pData);
  737. static CControlUI* CALLBACK __FindControlFromClass(CControlUI* pThis, LPVOID pData);
  738. static CControlUI* CALLBACK __FindControlsFromClass(CControlUI* pThis, LPVOID pData);
  739. static CControlUI* CALLBACK __FindControlsFromUpdate(CControlUI* pThis, LPVOID pData);
  740. static void AdjustSharedImagesHSL();
  741. void AdjustImagesHSL();
  742. void PostAsyncNotify();
  743. private:
  744. CDuiString m_sName;
  745. HWND m_hWndPaint; //所附加的窗体的句柄
  746. HDC m_hDcPaint;
  747. HDC m_hDcOffscreen;
  748. HDC m_hDcBackground;
  749. HBITMAP m_hbmpOffscreen;
  750. BYTE* m_pOffscreenBits;
  751. HBITMAP m_hbmpBackground;
  752. COLORREF* m_pBackgroundBits;
  753. // 提示信息
  754. HWND m_hwndTooltip;
  755. TOOLINFO m_ToolTip;
  756. int m_iHoverTime;
  757. bool m_bNoActivate;
  758. bool m_bShowUpdateRect;
  759. // 触屏状态(库级统一接管后,每个窗口的 CPaintManagerUI 各持一份)
  760. // 鼠标点击仍由 WM_LBUTTONDOWN/UP 维护 m_pEventClick;触摸期间则由
  761. // m_pEventTouch 和 m_bTouchMoved 独立维护,避免合成鼠标消息提前激活控件。
  762. CControlUI* m_pEventTouch = NULL; // 本次触摸命中的控件
  763. CContainerUI* m_pTouchScrollContainer = NULL; // 本次触摸对应的最近可滚动容器
  764. POINT m_ptLastTouchPos = { 0, 0 }; // 上一包触点:用于去重 + 计算跟手滚动的位移增量
  765. // 【已上移为线程级】原先这里有一个 `POINT m_ptLastTouchScreen` 成员,以及
  766. // 下方一个 `bool m_bSuppressHoverAfterTouch` 成员。二者现在都是
  767. // UIManager.cpp **文件级静态变量**(`s_ptLastTouchScreen` /
  768. // `s_bSuppressHoverAfterTouch`)。
  769. //
  770. // 为什么不能用成员:属性弹框**自带独立的 CPaintManagerUI m_pm**,与主窗口的
  771. // manager 不是同一个对象。在弹框上点「选好了」那次触摸记在**弹框**那份状态上;
  772. // 而弹框一销毁,系统补投的 `extra=0` WM_MOUSEMOVE 发给**主窗口**,落在主窗口的
  773. // manager 上 —— 那份状态不知道刚发生过触摸,±8px 判据必然失败 ⇒ 解除抑制 ⇒
  774. // 背后商品卡悬浮态残留。详见 UIManager.cpp 文件头的完整说明。
  775. POINT m_ptTouchStart = { 0, 0 }; // 本次手势起点:用于判断是否发生位移(拖拽 vs 轻点)
  776. bool m_bTouchMoved = false; // 本次触摸是否发生位移(>阈值视为拖拽),松开时抑制点击
  777. bool m_bSuppressPromotedTouchMouse = false; // 触摸点击已由 TOUCHUP 处理,丢弃随后提升的鼠标点击
  778. // 本次 TOUCHDOWN 的时间戳。若 TOUCHUP 丢失(激活/前台被抢、手势被系统中断、窗口被遮挡等),
  779. // m_pEventTouch / m_bSuppressPromotedTouchMouse 会永久残留 → 之后所有提升的鼠标消息都被吞,
  780. // 该窗口表现为“突然完全点不动”。用这个时间戳做超时自愈(见 MessageHandler 开头)。
  781. DWORD m_dwTouchDownTick = 0;
  782. // m_bSuppressPromotedTouchMouse 被置真时的时刻。
  783. // 用途:该抑制位有可能在【没有 TOUCHDOWN】的情况下被置真(两条输入流交叉时),
  784. // 此时 m_dwTouchDownTick 仍为 0,若只按它计时则自愈永远不触发 → 点击永久失效。
  785. // 所以单独记一个时间戳,保证"只要有抑制位,就一定会被超时复位"。
  786. DWORD m_dwSuppressTick = 0;
  787. // 手指抬起后,Windows 仍会补发合成的 WM_MOUSEMOVE;且真实 WM_MOUSELEAVE 在
  788. // m_bMouseTracking 为真时会【自己给自己重发 WM_MOUSEMOVE】(UIManager.cpp WM_MOUSELEAVE 分支),
  789. // 导致刚清掉的悬浮态立刻被重新点亮 → “悬浮态不消失 / 多个控件同时悬浮”。
  790. // 抬起后置 true,丢弃随后这批“非真实”的 MOUSEMOVE 悬浮逻辑;一旦收到用户真实移动鼠标
  791. // (这里用“距上次触摸位置明显移动”判定)即解除。
  792. // 注:这是【第二道】保险。第一道(根治)在 WM_MOUSEMOVE 里:凡带 MOUSEEVENTF_FROMTOUCH
  793. // 标记的“触摸提升出来的鼠标消息”一律不参与悬浮 enter/leave —— 触摸没有“光标停留”语义,
  794. // 让这类消息参与必然出现“点过的控件一直高亮 / 多点几个后多个同时高亮”。
  795. //
  796. // 【已上移为线程级】该标志原为成员 `m_bSuppressHoverAfterTouch`,现改为
  797. // UIManager.cpp 文件级静态变量 `s_bSuppressHoverAfterTouch`。
  798. // 原因:补投的 `extra=0` WM_MOUSEMOVE 可能落在**另一个 manager** 上
  799. // (属性弹框自带独立 m_pm),成员级状态会“看不见刚刚的触摸”。
  800. // 完整说明见 UIManager.cpp 文件头,以及本头文件 `m_ptLastTouchPos` 上方的注释。
  801. // [对标 nim_duilib] 竖向滑动 → WM_MOUSEWHEEL 的累加器。
  802. // 触摸只有位移增量,没有滚轮事件;把 TOUCHEVENTF_MOVE 的 Δy 累加,每满一个
  803. // WHEEL_DELTA(120) 就合成一次 UIEVENT_SCROLLWHEEL,让只认滚轮的控件在触摸下也能滚动。
  804. // 手势开始时(TOUCHDOWN)与手势结束时(TOUCHUP)都应清零,避免余量串到下一次手势。
  805. int m_nTouchWheelAccum = 0;
  806. // [通道无关滑动] 本次手势的滑动状态是否已由 BeginTouchSlideGesture 建立。
  807. //
  808. // 用途:指针通道(WM_POINTER*)在 DOWN 时也要走一遍 BeginTouchSlideGesture,
  809. // 才能在 UPDATE 里正确算出 Δ 并驱动容器/滚动条;UP 时也要走
  810. // EndTouchSlideGesture 来收尾并拿到"是否算轻点"。
  811. // 这个标志把"滑动状态机是否活着"与"本次手势由哪条通道驱动"解耦 ——
  812. // 两条通道都只是驱动者,滑动逻辑本身是共享的。
  813. bool m_bTouchSlideActive = false;
  814. // [通道混合] 是否启用 WM_POINTER*(Win8+ 指针栈)归一化。
  815. //
  816. // 为什么需要这个开关:Win10 上同一次触摸可能同时产生
  817. // ① 提升出来的鼠标消息 WM_LBUTTONDOWN/UP
  818. // ② 指针消息 WM_POINTERDOWN/UP
  819. // ③ legacy 触摸消息 WM_TOUCH
  820. // 如果把这些一起归一化成鼠标消息,同一次点击会驱动多套点击状态机 ⇒ 互相吞 →
  821. // 【所有窗口点击全失效】(含主窗口、含真实鼠标)。实测踩过这个坑。
  822. //
  823. // ⚠️ 历史教训(务必别再改回"二选一"):
  824. // 第一版策略是"默认关闭,收到真实 WM_TOUCH 就【永久】关闭本窗口的指针通道"。
  825. // 结果主窗口修好了,真模态弹框却彻底没救:
  826. // 本台收银机上 WM_TOUCH 的"贪婪归属权"(MSDN:收到第一条触摸消息后,
  827. // 后续所有触摸消息都发给该窗口,直到【另一个窗口获得焦点】)卡在主窗口身上,
  828. // 弹框(被程序 SetFocus,不是触摸点出来的焦点)永远拿不到 WM_TOUCH,
  829. // 只拿得到 WM_POINTER*(指针消息按窗口命中投递,不受贪婪归属权约束);
  830. // 而指针通道被永久关掉 ⇒ 弹框既无 WM_TOUCH 又无指针 ⇒ 完全点不动。
  831. //
  832. // 现在的策略:【始终开启 + 手势级让路(handover)】
  833. // · 指针通道永远开着(本开关默认 true),保证弹框这条"只有指针"的窗口有活路;
  834. // · 谁先驱动本次手势,谁就赢:指针先 DOWN ⇒ 本次手势内吃掉 WM_TOUCH;
  835. // WM_TOUCH 先 DOWN ⇒ 本次手势内拒绝指针;
  836. // · 手势结束(UP/超时/被夺)后立刻解禁 —— 下一个手势可以换通道。
  837. // 让路只加在**开始**(DOWN)上,因为同一手势两条通道的 DOWN 才是一对重复;
  838. // UP/MOVE 侧不做让路,避免某个通道的 UP 被误丢导致点一直悬着、成对状态卡死。
  839. // ⚠️ 这条规则是踩过坑之后补上的、不可省:曾经让路连 WM_POINTERUP 一起吞,
  840. // 而 UP 是唯一能解除 m_bPointerGestureActive 的消息 ⇒ 状态永久为真
  841. // ⇒ 之后每个 WM_POINTERDOWN 都被当"重复按下"挡掉 ⇒【整个窗口触摸点击全灭】。
  842. // 现在 UP 一律放行到 OnPointerInput,由它决定"只清状态"(让路中)还是"派发点击"。
  843. // 状态的"自愈"由 3 秒超时兜底(见下面的 m_dwPointerDownTick)。
  844. bool m_bPointerNormalizeEnabled = true;
  845. // ---- [NIM 风格] 通道归属:两个标记 + 一个计时器 ----
  846. //
  847. // 这套结构对照 nim_duilib Core/Window.cpp 的 OnPointerMsgs / HandleMessage:
  848. // · nim 用 m_pEventTouch(WM_TOUCH 占用)与 m_pEventPointer(指针占用)两个
  849. // **控件指针**做归属标记,谁先建立谁赢,另一条通道看到标记即退让;
  850. // · 本项目的等价物:m_pEventTouch(触摸占用)与 m_bPointerGestureActive(指针占用)。
  851. //
  852. // ⚠️⚠️ 最关键的教训:【不要让同一个字段同时表示"归属"和"计时"】⚠️⚠️
  853. // 曾经用一个 m_dwSuppressPointerUntilUpTick 时间戳身兼两职,结果
  854. // (GetTickCount() - 该值) < 3000 恒成立 ⇒ 指针通道自己驱动的手势被自己判定为
  855. // "让路中" ⇒ UP 走"只清状态、不合成点击"的早退路 ⇒ 点击全灭(真机日志可复现)。
  856. // 现在严格分离:归属用 bool 标记,计时用专用 tick 字段,互不干扰。
  857. //
  858. // m_bPointerGestureActive:
  859. // 本窗口当前正在由【指针通道】驱动一次手势(已 DOWN、还没 UP)。
  860. // 为真期间,进入 OnTouchInput 的 WM_TOUCH 一律忽略(含其 TOUCHDOWN/TOUCHUP)。
  861. bool m_bPointerGestureActive = false;
  862. //
  863. // m_dwActivePointerId:
  864. // m_bPointerGestureActive 为真时,对应的是哪个 pointerId(wParam 低 16 位)。
  865. // 多指时不相关的第二、三指消息直接放行/忽略,不会把状态机搅乱。
  866. DWORD m_dwActivePointerId = 0;
  867. //
  868. // m_dwPointerDownTick:
  869. // 指针手势的开始时刻,**仅**用于 3 秒超时自愈。
  870. // ⚠️ 与 m_dwActivePointerId 配套,只在指针 DOWN 时置、在 UP/CAPTURECHANGED/超时时清。
  871. // 绝不参与任何"归属判定"——归属只看 m_bPointerGestureActive。
  872. DWORD m_dwPointerDownTick = 0;
  873. // ---- "补调 DefWindowProc" 钩子(修复弹框收不到 WM_TOUCH)----
  874. // 见上方 SetDefProcRelay / RelayDefWindowProc 的长注释:
  875. // 被吞掉的触摸提升 WM_MOUSEMOVE 需要补走一次原窗口过程,
  876. // 否则系统的 WM_TOUCH "翻译"链会断掉,该窗口永远收不到 WM_TOUCH。
  877. PFN_RELAY_DEFPROC m_pfnDefProcRelay = NULL; // 由窗口过程注册(一般为 m_OldWndProc 的包装)
  878. HWND m_hWndDefProcRelay = NULL; // 该钩子绑定的窗口(换窗口时校验用)
  879. // 开关:**默认关闭**。开启后 MessageHandler 在吞掉那两条触摸提升
  880. // WM_MOUSEMOVE 前会补调一次原窗口过程,把系统的 WM_TOUCH 翻译链接上。
  881. //
  882. // 为什么默认关:这是一个"未经本机验证"的机制假设,贸然默认开启风险不可控
  883. // (最坏情况是主窗口本来正常的 WM_TOUCH 反而被干扰)。
  884. //
  885. // 如何开启(无需重新编译):
  886. // 设环境变量 `DUI_RELAY_DEFPROC=1` 再启动程序即可(见构造函数里的读取)。
  887. // 也可以在代码里调 SetDefProcRelayEnabled(true)。
  888. bool m_bRelayDefProcForTouchMove = false;
  889. //
  890. CControlUI* m_pRoot;
  891. CControlUI* m_pFocus;
  892. CControlUI* m_pEventHover;
  893. CControlUI* m_pEventClick;
  894. CControlUI* m_pEventRClick;
  895. CControlUI* m_pEventKey;
  896. CControlUI* m_pLastToolTip;
  897. //
  898. POINT m_ptLastMousePos;
  899. SIZE m_szMinWindow;
  900. SIZE m_szMaxWindow;
  901. SIZE m_szInitWindowSize;
  902. RECT m_rcSizeBox;
  903. SIZE m_szRoundCorner;
  904. RECT m_rcCaption;
  905. UINT m_uTimerID;
  906. bool m_bFirstLayout;
  907. bool m_bUpdateNeeded;
  908. bool m_bFocusNeeded;
  909. bool m_bOffscreenPaint;
  910. BYTE m_nOpacity;
  911. bool m_bLayered;
  912. RECT m_rcLayeredInset;
  913. bool m_bLayeredChanged;
  914. RECT m_rcLayeredUpdate;
  915. TDrawInfo m_diLayered;
  916. // 【抗锯齿圆角】roundcorner>0 自动启用的「分层渲染 + 内存圆角蒙版」模式。
  917. // 见 UIManager.cpp SetRoundCorner / FillRoundRectAlphaMask / WM_PAINT 处的长注释。
  918. bool m_bLayeredRoundMask;
  919. // 【最大化铺满整屏】由 SetRoundCornerSuppressed() 置位:让圆角模块"当作半径 0",
  920. // 从而蒙版退化成整块矩形、窗口四角直角(最大化铺满全屏用)。
  921. bool m_bRoundCornerSuppressed;
  922. // 【R37】本窗口是否曾调用过 SetLayeredWindowAttributes(LWA_ALPHA) 而进入
  923. // 「系统重定向」模式。MSDN SetLayeredWindowAttributes 备注原文:
  924. // "once SetLayeredWindowAttributes has been called for a layered window,
  925. // subsequent UpdateLayeredWindow calls will fail until the layering style
  926. // bit is cleared and set again."
  927. // 两种 alpha 机制互斥:SLA 的常量 alpha 与 ULW 的逐像素 alpha 不能共存。
  928. // 只影响本对象(每个窗口一个 CPaintManagerUI 实例)。
  929. bool m_bLayeredConstAlpha;
  930. // [2026-09-26 R31] 「本帧画完要把阴影补刷一次」的待办标志。
  931. // 圆角弧的颜色补偿要读 m_pBackgroundBits,而它在本帧 WM_PAINT 的调制段之后才第一次
  932. // 有内容 —— 蒙版首建 / 重建后置位,ULW 之后补刷一次 CShadowUI::Update(),
  933. // 否则弧上留的是旧的纯黑波段值(就是那圈偏亮的白弧)。
  934. bool m_bShadowContentPending;
  935. bool m_bMouseTracking;
  936. bool m_bMouseCapture;
  937. bool m_bIsPainting;
  938. bool m_bUsedVirtualWnd;
  939. bool m_bAsyncNotifyPosted;
  940. //
  941. CStdPtrArray m_aNotifiers;
  942. CStdPtrArray m_aTimers;
  943. CStdPtrArray m_aTranslateAccelerator;
  944. CStdPtrArray m_aPreMessageFilters;
  945. CStdPtrArray m_aMessageFilters;
  946. CStdPtrArray m_aPostPaintControls;
  947. CStdPtrArray m_aNativeWindow;
  948. CStdPtrArray m_aNativeWindowControl;
  949. CStdPtrArray m_aDelayedCleanup;
  950. CStdPtrArray m_aAsyncNotify;
  951. CStdPtrArray m_aFoundControls;
  952. CStdPtrArray m_aFonts;
  953. CStdPtrArray m_aNeedMouseLeaveNeeded;
  954. CStdStringPtrMap m_mNameHash;
  955. CStdStringPtrMap m_mWindowCustomAttrHash;
  956. CStdStringPtrMap m_mOptionGroup;
  957. bool m_bForceUseSharedRes;
  958. TResInfo m_ResInfo;
  959. // 窗口阴影
  960. CShadowUI m_shadow;
  961. // DPI管理器
  962. CDPI* m_pDPI;
  963. // 是否开启Gdiplus
  964. bool m_bUseGdiplusText;
  965. int m_trh;
  966. // 【第 47 轮】GDI+ 已提升为【进程级】(实现见 UIManager.cpp 文件头的
  967. // "进程级 GDI+ 运行时")。下面两个成员保留仅为【不改变本类布局】——
  968. // 本类被业务侧按值内联持有(CToastWnd::m_pm / CMainWnd::m_pm),
  969. // 删成员会让业务 exe 必须重编。它们现在的含义是"借用的只读快照":
  970. // · m_gdiplusToken :进程级 token 的快照,仅供调试观察;
  971. // ★ 禁止再传给 GdiplusShutdown(本类不再做收尾)
  972. // · m_pGdiplusStartupInput :指向文件级静态对象;
  973. // ★ 绝对禁止 delete(本类不拥有它)
  974. ULONG_PTR m_gdiplusToken;
  975. Gdiplus::GdiplusStartupInput *m_pGdiplusStartupInput;
  976. // 拖拽
  977. bool m_bDragDrop;
  978. bool m_bDragMode;
  979. HBITMAP m_hDragBitmap;
  980. IDragDropUI *m_pDragDrop;
  981. //
  982. static HINSTANCE m_hInstance;
  983. static HINSTANCE m_hResourceInstance;
  984. static CDuiString m_pStrResourcePath;
  985. static CDuiString m_pStrResourceZip;
  986. static CDuiString m_pStrResourceZipPwd;
  987. static HANDLE m_hResourceZip;
  988. static BYTE* m_cbZipBuf;
  989. static bool m_bCachedResourceZip;
  990. static int m_nResType;
  991. static TResInfo m_SharedResInfo;
  992. static bool m_bUseHSL;
  993. static short m_H;
  994. static short m_S;
  995. static short m_L;
  996. static CStdPtrArray m_aPreMessages;
  997. static CStdPtrArray m_aPlugins;
  998. };
  999. } // namespace DuiLib
  1000. #endif // __UIMANAGER_H__