|
|
@@ -1,4 +1,5 @@
|
|
|
#include "StdAfx.h"
|
|
|
+#include "UIBase.h"
|
|
|
#include "UIManager.h"
|
|
|
|
|
|
#ifdef _DEBUG
|
|
|
@@ -13,9 +14,9 @@ namespace DuiLib {
|
|
|
#ifndef WM_TOUCH
|
|
|
#define WM_TOUCH 0x0240
|
|
|
#endif
|
|
|
-// WM_POINTER* 系列(Win8+),同样不在 XP 段里。仅用于模态期间屏蔽 owner 的输入。
|
|
|
-#define DUI_WM_POINTER_FIRST 0x0246
|
|
|
-#define DUI_WM_POINTER_LAST 0x0259
|
|
|
+// 注:WM_POINTER* 常量统一放在 UIManager.cpp 的 PointerMsg 命名空间里(那边才是处理方),
|
|
|
+// 本文件不再重复定义 —— 原先为"模态期间屏蔽 owner 输入"定义的 DUI_WM_POINTER_FIRST/LAST
|
|
|
+// 已随 IsModalBlockedInputMessage 一并删除。
|
|
|
|
|
|
namespace {
|
|
|
typedef BOOL (WINAPI* PFN_REGISTER_TOUCH_WINDOW)(HWND, ULONG);
|
|
|
@@ -47,6 +48,13 @@ namespace {
|
|
|
pfnRegister(hWnd, 0);
|
|
|
}
|
|
|
|
|
|
+ // 【已移除】RefreshTouchRegistration(Unregister + Register 强制重算触摸归属)。
|
|
|
+ //
|
|
|
+ // 它曾是"假模态 + WM_TOUCH 贪婪归属权"时代的补丁:试图用注销/重注册把触摸归属权
|
|
|
+ // 从 owner 重新结算到弹框头上。实测无效(弹框依然零 WM_TOUCH),且真模态方案下
|
|
|
+ // 完全没必要 —— 对话框窗口是新 HWND,创建时 RegisterTouchWindowIfAvailable 注册一次即可,
|
|
|
+ // 系统自己就把触摸投给它。留着只会带来 C4505(未引用函数)警告,故整段删除。
|
|
|
+
|
|
|
// 诊断:WM_TOUCH 究竟落在哪个窗口上(弹框自己?还是被禁用/被挡住的 owner?)。
|
|
|
// ⚠️ 只 peek,绝不调用 CloseTouchInputHandle:句柄必须交给 CPaintManagerUI::OnTouchInput 关闭。
|
|
|
void TraceTouchArrival(HWND hWnd, WPARAM wParam, LPARAM lParam)
|
|
|
@@ -73,20 +81,171 @@ namespace {
|
|
|
(void*)DuiLib::CPaintManagerUI::GetModalWindow(), (void*)::GetForegroundWindow());
|
|
|
}
|
|
|
|
|
|
- // 上一次"抢回前台"的时间戳(限流用,避免频繁改激活状态打断输入)
|
|
|
- DWORD s_dwLastModalFocusFix = 0;
|
|
|
+ // ---------------------------------------------------------------------------
|
|
|
+ // 【临时全量诊断】记录系统到底把哪些"输入类消息"发给了哪个窗口。
|
|
|
+ //
|
|
|
+ // 背景:弹框打开后,日志里既没有 WM_TOUCH(任何窗口都没有),也没有 WM_LBUTTONDOWN/UP,
|
|
|
+ // 却能看到弹框收到 WM_MOUSEMOVE(hover)。这说明触摸可能被系统降级成了鼠标模拟,
|
|
|
+ // 或者走了 WM_GESTURE / WM_POINTER* 另外两条通道。这三个通道目前都没被记录到,
|
|
|
+ // 因此需要一段覆盖面更广的诊断来确定"消息到底有没有发出来、发给了谁"。
|
|
|
+ //
|
|
|
+ // 覆盖:WM_TOUCH(0x0240) / WM_GESTURE(0x0119) / WM_POINTER*(0x0245~0x025F 及 0x0246/0x0247 变体)
|
|
|
+ // WM_MOUSEMOVE(0x0200) / WM_LBUTTONDOWN(0x0201) / WM_LBUTTONUP(0x0202)
|
|
|
+ // WM_NCMOUSEMOVE(0x00A0) / WM_MOUSEACTIVATE(0x0021) / WM_SETCURSOR(0x0020)
|
|
|
+ //
|
|
|
+ // ⚠️ 纯只读诊断:不改消息流向、不返回值、不消费句柄。只为定位问题,验证完请删除。
|
|
|
+ // ---------------------------------------------------------------------------
|
|
|
+ // MSG-ID 常量(不引用任何 WM_* 宏,避免 _WIN32_WINNT 版本段差异导致编译不过)
|
|
|
+ const UINT DUI_MSG_WM_TOUCH = 0x0240;
|
|
|
+ const UINT DUI_MSG_WM_GESTURE = 0x0119;
|
|
|
+ const UINT DUI_MSG_WM_GESTURENOTIFY = 0x011A;
|
|
|
+ const UINT DUI_MSG_WM_MOUSEMOVE = 0x0200;
|
|
|
+ const UINT DUI_MSG_WM_LBUTTONDOWN = 0x0201;
|
|
|
+ const UINT DUI_MSG_WM_LBUTTONUP = 0x0202;
|
|
|
+ const UINT DUI_MSG_WM_LBUTTONDBLCLK = 0x0203;
|
|
|
+ const UINT DUI_MSG_WM_RBUTTONDOWN = 0x0204;
|
|
|
+ const UINT DUI_MSG_WM_RBUTTONUP = 0x0205;
|
|
|
+ const UINT DUI_MSG_WM_NCMOUSEMOVE = 0x00A0;
|
|
|
+ const UINT DUI_MSG_WM_NCLBUTTONDOWN = 0x00A1;
|
|
|
+ const UINT DUI_MSG_WM_MOUSEACTIVATE = 0x0021;
|
|
|
+ const UINT DUI_MSG_WM_SETCURSOR = 0x0020;
|
|
|
+ const UINT DUI_MSG_WM_NCHITTEST = 0x0084;
|
|
|
+ const UINT DUI_MSG_WM_ACTIVATE = 0x0006;
|
|
|
+ const UINT DUI_MSG_WM_ACTIVATEAPP = 0x001C;
|
|
|
+ const UINT DUI_MSG_WM_SETFOCUS = 0x0007;
|
|
|
+ const UINT DUI_MSG_WM_KILLFOCUS = 0x0008;
|
|
|
+
|
|
|
+ void TraceInputMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
|
+ {
|
|
|
+ LPCTSTR pszName = NULL;
|
|
|
+ switch (uMsg)
|
|
|
+ {
|
|
|
+ // ⚠️ case 必须是"编译期常量表达式"。这些常量在匿名 namespace 里是 const UINT
|
|
|
+ // 且已初始化,属于 ICE,可以安全用作 case 标签。
|
|
|
+ case DUI_MSG_WM_TOUCH: pszName = _T("WM_TOUCH"); break;
|
|
|
+ case DUI_MSG_WM_GESTURE: pszName = _T("WM_GESTURE"); break;
|
|
|
+ case DUI_MSG_WM_GESTURENOTIFY: pszName = _T("WM_GESTURENOTIFY"); break;
|
|
|
+ case DUI_MSG_WM_MOUSEMOVE: pszName = _T("WM_MOUSEMOVE"); break;
|
|
|
+ case DUI_MSG_WM_LBUTTONDOWN: pszName = _T("WM_LBUTTONDOWN"); break;
|
|
|
+ case DUI_MSG_WM_LBUTTONUP: pszName = _T("WM_LBUTTONUP"); break;
|
|
|
+ case DUI_MSG_WM_LBUTTONDBLCLK: pszName = _T("WM_LBUTTONDBLCLK"); break;
|
|
|
+ case DUI_MSG_WM_RBUTTONDOWN: pszName = _T("WM_RBUTTONDOWN"); break;
|
|
|
+ case DUI_MSG_WM_RBUTTONUP: pszName = _T("WM_RBUTTONUP"); break;
|
|
|
+ case DUI_MSG_WM_NCMOUSEMOVE: pszName = _T("WM_NCMOUSEMOVE"); break;
|
|
|
+ case DUI_MSG_WM_NCLBUTTONDOWN: pszName = _T("WM_NCLBUTTONDOWN"); break;
|
|
|
+ case DUI_MSG_WM_MOUSEACTIVATE: pszName = _T("WM_MOUSEACTIVATE"); break;
|
|
|
+ case DUI_MSG_WM_SETCURSOR: pszName = _T("WM_SETCURSOR"); break;
|
|
|
+ case DUI_MSG_WM_NCHITTEST: pszName = _T("WM_NCHITTEST"); break;
|
|
|
+ case DUI_MSG_WM_ACTIVATE: pszName = _T("WM_ACTIVATE"); break;
|
|
|
+ case DUI_MSG_WM_ACTIVATEAPP: pszName = _T("WM_ACTIVATEAPP"); break;
|
|
|
+ case DUI_MSG_WM_SETFOCUS: pszName = _T("WM_SETFOCUS"); break;
|
|
|
+ case DUI_MSG_WM_KILLFOCUS: pszName = _T("WM_KILLFOCUS"); break;
|
|
|
+ default: break;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 覆盖 WM_POINTER* 一段(0x0245~0x0260,不同 SDK 版本消息号有差异,统一按范围判断)
|
|
|
+ const bool bPointerMsg = (uMsg >= 0x0245 && uMsg <= 0x0260);
|
|
|
+ if (pszName == NULL && !bPointerMsg) return;
|
|
|
+
|
|
|
+ // WM_MOUSEMOVE / WM_NCMOUSEMOVE / WM_NCHITTEST / WM_SETCURSOR 极其频繁,
|
|
|
+ // 全量记录会瞬间刷爆日志,这里每类限流 300ms。
|
|
|
+ const bool bNoisy = (uMsg == DUI_MSG_WM_MOUSEMOVE || uMsg == DUI_MSG_WM_NCMOUSEMOVE
|
|
|
+ || uMsg == DUI_MSG_WM_NCHITTEST || uMsg == DUI_MSG_WM_SETCURSOR);
|
|
|
+ if (bNoisy)
|
|
|
+ {
|
|
|
+ static DWORD s_dwLastNoisyTick = 0;
|
|
|
+ static UINT s_uLastNoisyMsg = 0;
|
|
|
+ const DWORD dwNow = ::GetTickCount();
|
|
|
+ if (s_uLastNoisyMsg == uMsg && (LONG)(dwNow - s_dwLastNoisyTick) < 300) return;
|
|
|
+ s_dwLastNoisyTick = dwNow;
|
|
|
+ s_uLastNoisyMsg = uMsg;
|
|
|
+ }
|
|
|
+
|
|
|
+ DWORD dwExtra = (DWORD)(DWORD_PTR)::GetMessageExtraInfo();
|
|
|
+ DuiLib::TouchTrace::Log(_T("INPUT-TRACE target=%p msg=0x%04X %s w=0x%08X l=0x%08X extra=0x%08X fromtouch=%d modal=%p fg=%p enable=%d"),
|
|
|
+ (void*)hWnd, uMsg, pszName != NULL ? pszName : _T("WM_POINTER*"),
|
|
|
+ (unsigned)wParam, (unsigned)lParam, (unsigned)dwExtra,
|
|
|
+ (int)((dwExtra & 0xFFFFFF80) == 0xFF515700),
|
|
|
+ (void*)DuiLib::CPaintManagerUI::GetModalWindow(),
|
|
|
+ (void*)::GetForegroundWindow(),
|
|
|
+ (int)::IsWindowEnabled(hWnd));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 【已移除】LogProcessWindowList / EnumProc_LogWnd(本进程顶层窗口清单,MODAL-WNDLIST)。
|
|
|
+ // 它们是排查"WM_TOUCH 究竟投给了哪个陌生 HWND"的临时诊断,只被已删除的假模态消息循环调用。
|
|
|
+ // 真模态方案下弹框自己就是触摸目标,这份清单不再需要,删除以免 C4505。
|
|
|
+
|
|
|
+ // ---------------------------------------------------------------------------
|
|
|
+ // 真模态(DialogBox)辅助:按 paint window 找到对应的 CPaintManagerUI。
|
|
|
+ //
|
|
|
+ // 用途:ShowModal 里要把 manager 从旧 HWND 迁到对话框新 HWND,需要先找到它。
|
|
|
+ // 遍历 CPaintManagerUI::GetPaintManagers(),比对 GetPaintWindow()。
|
|
|
+ // ---------------------------------------------------------------------------
|
|
|
+ DuiLib::CPaintManagerUI* FindManagerByPaintWindow(HWND hWnd)
|
|
|
+ {
|
|
|
+ if (hWnd == NULL) return NULL;
|
|
|
+ CStdPtrArray* pManagers = DuiLib::CPaintManagerUI::GetPaintManagers();
|
|
|
+ if (pManagers == NULL) return NULL;
|
|
|
+ for (int i = 0; i < pManagers->GetSize(); ++i)
|
|
|
+ {
|
|
|
+ DuiLib::CPaintManagerUI* pManager = static_cast<DuiLib::CPaintManagerUI*>(pManagers->GetAt(i));
|
|
|
+ if (pManager != NULL && pManager->GetPaintWindow() == hWnd) return pManager;
|
|
|
+ }
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ---------------------------------------------------------------------------
|
|
|
+ // [WM_TOUCH 归属权] "补调原窗口过程"的中转函数。
|
|
|
+ //
|
|
|
+ // 为什么需要它:
|
|
|
+ // WM_TOUCH 不是被系统直接投递的。系统先发一条"特殊标记的 WM_MOUSEMOVE"
|
|
|
+ // (开了 raw input 时是 WM_INPUT),只有这条消息走到 DefWindowProc 里,
|
|
|
+ // DefWindowProc 才会把驻留的 WM_TOUCH "翻译"并派发出来
|
|
|
+ // (来源:The Witness 作者的逆向结论;MSDN《Architectural Overview》的
|
|
|
+ // greedy 段落也印证了这套投递模型)。
|
|
|
+ // 本库的 CPaintManagerUI::MessageHandler 为了不让"触摸提升出来的鼠标移动"
|
|
|
+ // 破坏拖拽/悬浮状态机,会吞掉它(return true),从而跳过了 DefWindowProc
|
|
|
+ // ⇒ 窗口(尤其 #32770 真模态弹框,它依赖 DefDlgProc→DefWindowProc 这条链)
|
|
|
+ // 后续永远收不到 WM_TOUCH。
|
|
|
+ //
|
|
|
+ // 这个中转函数由 CPaintManagerUI::RelayDefWindowProc 回调,
|
|
|
+ // 作用就是"把消息喂给原窗口过程一次",补上被跳过的那次副作用。
|
|
|
+ // 用静态变量保存"当前转发目标":因为回调签名不带 this,
|
|
|
+ // 而每个窗口过程在同一时刻只有一个绑定关系(主窗口 or 某个弹框)。
|
|
|
+ // 真模态切换 HWND 时通过 SetDefProcRelay 重新绑定,不会串。
|
|
|
+ //
|
|
|
+ // ⚠️ 位置要求:必须定义在 __DialogWndProc 之前(它比 __WndProc 先出现)。
|
|
|
+ // ---------------------------------------------------------------------------
|
|
|
+ WNDPROC g_pfnRelayTarget = NULL;
|
|
|
+ HWND g_hWndRelayBound = NULL;
|
|
|
+
|
|
|
+ LRESULT CALLBACK RelayWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
|
+ {
|
|
|
+ // 只有"当前正在转发的窗口"才有效:避免悬垂到已销毁窗口的过程上。
|
|
|
+ if (g_pfnRelayTarget == NULL || g_hWndRelayBound != hWnd)
|
|
|
+ return 0;
|
|
|
+ return ::CallWindowProc(g_pfnRelayTarget, hWnd, uMsg, wParam, lParam);
|
|
|
+ }
|
|
|
|
|
|
- // 模态期间需要屏蔽的输入类消息(发给被弹框挡住的 owner 时直接丢弃,不再派发)
|
|
|
- bool IsModalBlockedInputMessage(UINT uMsg)
|
|
|
+ // 把"原窗口过程"登记为转发目标(绑定到指定 HWND)。
|
|
|
+ void BindRelayTarget(WNDPROC pfnOldProc, HWND hWnd)
|
|
|
{
|
|
|
- if (uMsg == WM_TOUCH) return true; // 0x0240
|
|
|
- if (uMsg >= DUI_WM_POINTER_FIRST && uMsg <= DUI_WM_POINTER_LAST) return true;
|
|
|
- if (uMsg >= 0x0200 && uMsg <= 0x020E) return true; // WM_MOUSEMOVE .. WM_MOUSELAST
|
|
|
- if (uMsg >= 0x00A0 && uMsg <= 0x00AD) return true; // WM_NCMOUSEMOVE .. WM_NCXBUTTONDBLCLK
|
|
|
- if (uMsg >= WM_KEYFIRST && uMsg <= WM_KEYLAST) return true; // 键盘
|
|
|
- if (uMsg == WM_MOUSEACTIVATE) return true;
|
|
|
- return false;
|
|
|
+ g_pfnRelayTarget = pfnOldProc;
|
|
|
+ g_hWndRelayBound = hWnd;
|
|
|
}
|
|
|
+
|
|
|
+ // DetachPaintWindow 之后、AttachPaintWindow 之前,用这个取出"待挂载的 manager"。
|
|
|
+ // 因为此时 manager 已从 m_aPreMessages 摘除,FindManagerByPaintWindow 找不到它了,
|
|
|
+ // 所以用一个文件级静态变量中转(只在 ShowModal 的单线程窗口过程里使用,无需同步)。
|
|
|
+ static DuiLib::CPaintManagerUI* g_pDetachedManager = NULL;
|
|
|
+
|
|
|
+ void SetDetachedManager(DuiLib::CPaintManagerUI* pManager) { g_pDetachedManager = pManager; }
|
|
|
+ DuiLib::CPaintManagerUI* GetDetachedManager() { return g_pDetachedManager; }
|
|
|
+
|
|
|
+ // 【已移除】IsModalBlockedInputMessage —— 假模态时代的"在消息循环里丢弃发给 owner 的输入消息"。
|
|
|
+ // 现在 ShowModal 走真模态(DialogBoxIndirectParam),系统对话框管理器自带 owner 禁用,
|
|
|
+ // 这条兜底不但多余,还会误伤:真模态期间 owner 本就需要收到 WM_ACTIVATE/WM_KILLFOCUS 之类的
|
|
|
+ // 通知消息,无差别丢弃会让 owner 的状态机错乱。故整段删除,不再保留。
|
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////
|
|
|
@@ -295,7 +454,7 @@ void CNotifyPump::NotifyPump(TNotifyUI& msg)
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
///
|
|
|
-CWindowWnd::CWindowWnd() : m_hWnd(NULL), m_OldWndProc(::DefWindowProc), m_bSubclassed(false), m_bUnicode(false), m_bFakeModal(false)
|
|
|
+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()
|
|
|
{
|
|
|
}
|
|
|
|
|
|
@@ -390,101 +549,408 @@ void CWindowWnd::ShowWindow(bool bShow /*= true*/, bool bTakeFocus /*= false*/)
|
|
|
|
|
|
UINT CWindowWnd::ShowModal()
|
|
|
{
|
|
|
+ // =========================================================================
|
|
|
+ // 【真模态】改用 Windows 的对话框机制(DialogBoxIndirectParam)驱动模态。
|
|
|
+ //
|
|
|
+ // 参考:网易 nim_duilib 的 NativeWindow_Windows::DoModal()
|
|
|
+ // (duilib/Core/NativeWindow_Windows.cpp)
|
|
|
+ //
|
|
|
+ // ── 为什么必须换掉旧写法 ──────────────────────────────────────────────
|
|
|
+ // 旧实现是"假模态":自己 ShowWindow + EnableWindow(owner,FALSE) + TOPMOST +
|
|
|
+ // 抢前台 + 自写 while(GetMessage) 嵌套循环。在 Win10 触摸屏上必然失败,根因是
|
|
|
+ // 触摸消息的投递目标规则(MSDN, Getting Started with Windows Touch Messages):
|
|
|
+ //
|
|
|
+ // "WM_TOUCH messages are currently 'greedy.' After the first touch message is
|
|
|
+ // received on a window, all touch messages are sent to that window until
|
|
|
+ // another window receives focus."
|
|
|
+ //
|
|
|
+ // 主窗口早就 RegisterTouchWindow 过、也早就收到过 WM_TOUCH,所以触摸归属权长期
|
|
|
+ // 在它手里;弹框是自己 Create 出来的普通窗口,**不是**系统的触摸目标。
|
|
|
+ // 我们为此打了一堆补丁(手动焦点切换、刷新注册、消息循环转发、__WndProc 转发兜底),
|
|
|
+ // 实测全部无效:日志里弹框始终零 WM_TOUCH、零 WM_LBUTTONDOWN/UP。
|
|
|
+ //
|
|
|
+ // ── 对话框为什么能解决 ────────────────────────────────────────────────
|
|
|
+ // DialogBox 是内核实现的模态机制,它:
|
|
|
+ // · 由系统跑消息循环(不需要我们自写嵌套循环);
|
|
|
+ // · 由系统按正确时机禁用 owner(不需要我们 EnableWindow);
|
|
|
+ // · 由系统维持前台/激活(不需要 TOPMOST / SetForegroundWindow / 焦点切换);
|
|
|
+ // · **把对话框窗口本身变成输入目标**,触摸自然投给它、提升的鼠标消息也落在它身上。
|
|
|
+ //
|
|
|
+ // 于是"贪婪归属权""提升鼠标被吞""owner 抢前台""禁用窗口收不到消息"这几类问题
|
|
|
+ // 全部不存在 —— 不是被绕过,而是压根不成立。
|
|
|
+ //
|
|
|
+ // ── 如何与既有代码兼容 ────────────────────────────────────────────────
|
|
|
+ // 调用方约定是:new → Create(hwnd, ..., UI_WNDSTYLE_DIALOG, ...) → SetIcon →
|
|
|
+ // CenterWindow() → ShowModal()。也就是说**窗口在我们被调用之前就已经 Create 好了**,
|
|
|
+ // CPaintManagerUI 也已 Init 到那个 HWND 上。所以这里不能另起一个对话框窗口,
|
|
|
+ // 而是:用当前窗口的几何/样式造一个 DLGTEMPLATE,让 DialogBoxIndirectParam 去创建
|
|
|
+ // 对话框窗口,再把旧的 HWND 销毁、把 DuiLib 的 manager 迁移到新 HWND —— 这样
|
|
|
+ // 调用方完全无感知,`m_hWnd` 语义不变,30+ 个调用点一行都不用改。
|
|
|
+ // =========================================================================
|
|
|
+
|
|
|
ASSERT(::IsWindow(m_hWnd));
|
|
|
- UINT nRet = 0;
|
|
|
- HWND hWndParent = GetWindowOwner(m_hWnd);
|
|
|
- const bool bBlockParent = (hWndParent != NULL && ::IsWindow(hWndParent));
|
|
|
+ if( !::IsWindow(m_hWnd) ) return 0;
|
|
|
|
|
|
- RegisterTouchWindowIfAvailable(m_hWnd);
|
|
|
+ HWND hWndOwner = ::GetWindow(m_hWnd, GW_OWNER);
|
|
|
+ if( hWndOwner == NULL ) hWndOwner = ::GetActiveWindow();
|
|
|
|
|
|
- // -------------------------------------------------------------------------
|
|
|
- // 【重要】这里不再调用 ::EnableWindow(hWndParent, FALSE)。
|
|
|
+ // ---- 1) 采集旧窗口的几何与样式,用于构造对话框模板 ----
|
|
|
+ RECT rcWnd = { 0 };
|
|
|
+ ::GetWindowRect(m_hWnd, &rcWnd);
|
|
|
+ const int nW = rcWnd.right - rcWnd.left;
|
|
|
+ const int nH = rcWnd.bottom - rcWnd.top;
|
|
|
+
|
|
|
+ // 【尺寸精确性】旧窗口的客户区尺寸 —— 这是迁移后必须保持不变的量。
|
|
|
//
|
|
|
- // 原因:Windows 8/10 把触摸输入改成了 Pointer 栈,触摸消息(WM_TOUCH / WM_POINTER*)
|
|
|
- // 的投递目标与"窗口是否被禁用 / 是否是活动窗口"强相关。实测在 Win10 上,只要 owner
|
|
|
- // 被 EnableWindow(FALSE) 禁用,手势就会被系统转投给被禁用的 owner 而不再到达弹框,
|
|
|
- // 于是弹框既收不到 WM_TOUCH(滚动条无法触摸滑动)、也没有可靠的提升鼠标消息可用
|
|
|
- // (点击无反应);而在 Win7 上鼠标/触摸提升路径不受此影响,所以旧写法一直正常。
|
|
|
+ // 为什么不能直接用窗口矩形(nW/nH)去填 DLGTEMPLATE.cx/cy:
|
|
|
+ // 1. DLGTEMPLATE 的 cx/cy 描述的是**客户区**尺寸,不是窗口外框尺寸;
|
|
|
+ // 2. 我们给模板设的样式(WS_CAPTION/WS_DLGFRAME/DS_MODALFRAME…)会让系统
|
|
|
+ // 在此基础上再往外加一圈非客户区边框。
|
|
|
+ // 若直接填 nW/nH,新窗口 = 旧窗口 + 边框 ⇒ 弹框整体变大、位置也偏。
|
|
|
//
|
|
|
- // 模态性改用两条等价手段保证:
|
|
|
- // ① 弹框置顶 + 激活,抢到前台;
|
|
|
- // ② 消息循环里丢弃所有发给 owner(及其子窗口)的输入类消息(见 IsModalBlockedInputMessage)。
|
|
|
- // —— owner 保持 enabled,它的鼠标/键盘/触摸都不会被派发,等价于"点不动";
|
|
|
- // ③ CPaintManagerUI 层再加一道:有模态弹框时,非弹框窗口收到 WM_TOUCH 直接忽略。
|
|
|
- // -------------------------------------------------------------------------
|
|
|
- ::ShowWindow(m_hWnd, SW_SHOWNORMAL);
|
|
|
- ::SetWindowPos(m_hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
|
|
|
- ::BringWindowToTop(m_hWnd);
|
|
|
- ::SetForegroundWindow(m_hWnd);
|
|
|
- ::SetActiveWindow(m_hWnd);
|
|
|
- ::SetFocus(m_hWnd);
|
|
|
- // 显示之后再注册一次(幂等,重复调用无害):确保最终窗口样式下的触摸注册生效。
|
|
|
- RegisterTouchWindowIfAvailable(m_hWnd);
|
|
|
-
|
|
|
- // 支持弹框嵌套:保存外层模态窗口,退出时恢复
|
|
|
+ // 因此这里取"旧窗口客户区尺寸"作为目标客户区尺寸;创建完成后再在 __DialogProc 里
|
|
|
+ // 用 AdjustWindowRectEx 反算目标外框并 SetWindowPos 精确校正(见 __DialogProc)。
|
|
|
+ RECT rcClient = { 0 };
|
|
|
+ ::GetClientRect(m_hWnd, &rcClient);
|
|
|
+ const int nClientW = rcClient.right - rcClient.left;
|
|
|
+ const int nClientH = rcClient.bottom - rcClient.top;
|
|
|
+
|
|
|
+ DWORD dwStyle = (DWORD)::GetWindowLong(m_hWnd, GWL_STYLE);
|
|
|
+ DWORD dwExStyle = (DWORD)::GetWindowLong(m_hWnd, GWL_EXSTYLE);
|
|
|
+
|
|
|
+ // 对话框专用的样式:把我们自己那套 WS_POPUPWINDOW/WS_CAPTION/WS_DLGFRAME 换成
|
|
|
+ // 对话框该有的组合。WS_POPUP 必须保留(否则会变成带系统标题栏的普通窗口)。
|
|
|
+ // WS_VISIBLE 让 DialogBox 创建后直接显示。
|
|
|
+ //
|
|
|
+ // ⚠️ 关于 WS_CAPTION / DS_MODALFRAME:
|
|
|
+ // 本项目的弹框(UI_WNDSTYLE_DIALOG)是**无系统标题栏的自绘弹框**,且调用方
|
|
|
+ // 均未传 WS_THICKFRAME(固定尺寸)。所以这里**不能**自作主张加 DS_MODALFRAME 或
|
|
|
+ // WS_THICKFRAME —— 那会给弹框加一圈可调整大小的边框,既改变外观又改变尺寸。
|
|
|
+ // 保持 WS_POPUP + 原有的 WS_CAPTION/WS_SYSMENU 即可(自绘弹框本来就不显示系统标题栏)。
|
|
|
+ DWORD dwDlgStyle = WS_POPUP | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
|
|
|
+ if( dwStyle & WS_CAPTION ) dwDlgStyle |= WS_CAPTION | WS_SYSMENU;
|
|
|
+ if( dwStyle & WS_THICKFRAME ) dwDlgStyle |= WS_THICKFRAME;
|
|
|
+ if( dwStyle & WS_MINIMIZEBOX ) dwDlgStyle |= WS_MINIMIZEBOX;
|
|
|
+ if( dwStyle & WS_MAXIMIZEBOX ) dwDlgStyle |= WS_MAXIMIZEBOX;
|
|
|
+
|
|
|
+ // ex-style 基本原样透传(WS_EX_TOOLWINDOW / WS_EX_WINDOWEDGE 等),但清掉两类:
|
|
|
+ // · WS_EX_APPWINDOW —— 对话框不该出现在任务栏;
|
|
|
+ // · WS_EX_DLGMODALFRAME —— 会给弹框加一圈双线边框,固定尺寸自绘弹框不需要(会改变外框尺寸)。
|
|
|
+ dwExStyle &= ~(WS_EX_APPWINDOW | WS_EX_DLGMODALFRAME);
|
|
|
+
|
|
|
+ // ---- 2) 构造 DLGTEMPLATE 资源 ----
|
|
|
+ // 只用 DLGTEMPLATE 本体(不含菜单/类名/标题/字体),微软文档允许这样,
|
|
|
+ // 系统会套用默认的对话框字体与类(#32770)。
|
|
|
+ // 参考 nim_duilib:sizeof(DLGTEMPLATE) + 32 字节余量。
|
|
|
+ const size_t nDataSize = sizeof(DLGTEMPLATE) + 32;
|
|
|
+ HGLOBAL hResource = ::GlobalAlloc(GMEM_MOVEABLE, nDataSize);
|
|
|
+ if( hResource == NULL )
|
|
|
+ {
|
|
|
+ DuiLib::TouchTrace::Log(_T("MODAL-DLG GlobalAlloc failed err=%u"), ::GetLastError());
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ LPDLGTEMPLATE pTemplate = (LPDLGTEMPLATE)::GlobalLock(hResource);
|
|
|
+ if( pTemplate == NULL )
|
|
|
+ {
|
|
|
+ ::GlobalFree(hResource);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ ::ZeroMemory(pTemplate, nDataSize);
|
|
|
+ pTemplate->style = dwDlgStyle;
|
|
|
+ pTemplate->dwExtendedStyle = dwExStyle;
|
|
|
+ pTemplate->cdit = 0; // 不使用模板内控件(控件由 DuiLib 自绘)
|
|
|
+ // DLGTEMPLATE 的 cx/cy 单位是对话框单位(DLU),且语义为**客户区**尺寸。
|
|
|
+ // 这里只是给系统一个初始值(避免 0 尺寸导致奇怪的默认值),真正的精确尺寸
|
|
|
+ // 由 __DialogProc 里创建完成后按旧客户区尺寸 + AdjustWindowRectEx 反算并 SetWindowPos 校正。
|
|
|
+ pTemplate->x = 0;
|
|
|
+ pTemplate->y = 0;
|
|
|
+ pTemplate->cx = (short)(nClientW > 0 ? nClientW : nW);
|
|
|
+ pTemplate->cy = (short)(nClientH > 0 ? nClientH : nH);
|
|
|
+
|
|
|
+ // ---- 3) 迁移前先把旧的 HWND 记录下来,并让 manager 做好切换准备 ----
|
|
|
+ HWND hWndOld = m_hWnd;
|
|
|
+
|
|
|
+ m_bDoModal = true;
|
|
|
+ m_nModalRet = 0;
|
|
|
+
|
|
|
+ // 把"目标位置 + 目标客户区尺寸"暂存到成员,供 __DialogProc 在创建完成后精确校正。
|
|
|
+ // 之所以不在模板里一次算准:对话框的非客户区厚度取决于实际样式(WS_CAPTION/WS_DLGFRAME/
|
|
|
+ // WS_EX_* 组合),预估值容易差几个像素;创建后用 AdjustWindowRectEx 反算再 SetWindowPos
|
|
|
+ // 是唯一能保证"客户区尺寸与旧窗口逐像素一致"的做法。
|
|
|
+ m_rcModalTarget.left = rcWnd.left;
|
|
|
+ m_rcModalTarget.top = rcWnd.top;
|
|
|
+ m_rcModalTarget.right = rcWnd.left + nClientW;
|
|
|
+ m_rcModalTarget.bottom = rcWnd.top + nClientH;
|
|
|
+
|
|
|
+ DuiLib::TouchTrace::Log(_T("MODAL-DLG enter old=%p owner=%p wndrect=%d,%d,%dx%d client=%dx%d dlgstyle=0x%08X ex=0x%08X"),
|
|
|
+ (void*)hWndOld, (void*)hWndOwner, rcWnd.left, rcWnd.top, nW, nH, nClientW, nClientH,
|
|
|
+ (unsigned)dwDlgStyle, (unsigned)dwExStyle);
|
|
|
+
|
|
|
+ // 记录"当前模态窗口",供库内其它地方判断(如 OnTouchInput 的老兜底逻辑)。
|
|
|
+ // 注意:此时还不知道新 HWND,__DialogProc 里会再次 SetModalWindow。
|
|
|
HWND hPrevModal = CPaintManagerUI::GetModalWindow();
|
|
|
- CPaintManagerUI::SetModalWindow(m_hWnd);
|
|
|
- DuiLib::TouchTrace::Log(_T("MODAL-ENTER hwnd=%p owner=%p foreground=%p active=%p focus=%p enabled=%d style=0x%08X ex=0x%08X"),
|
|
|
- (void*)m_hWnd, (void*)hWndParent, (void*)::GetForegroundWindow(), (void*)::GetActiveWindow(),
|
|
|
- (void*)::GetFocus(), (int)::IsWindowEnabled(m_hWnd),
|
|
|
- (unsigned)::GetWindowLong(m_hWnd, GWL_STYLE), (unsigned)::GetWindowLong(m_hWnd, GWL_EXSTYLE));
|
|
|
-
|
|
|
- MSG msg = { 0 };
|
|
|
- while( ::IsWindow(m_hWnd) && ::GetMessage(&msg, NULL, 0, 0) ) {
|
|
|
- if( msg.message == WM_QUIT ) break;
|
|
|
-
|
|
|
- // 模态期间:owner 及其子窗口的任何输入消息都不派发(等价于原来的禁用效果)
|
|
|
- if( bBlockParent && msg.hwnd != NULL
|
|
|
- && (msg.hwnd == hWndParent || ::IsChild(hWndParent, msg.hwnd))
|
|
|
- && IsModalBlockedInputMessage(msg.message) )
|
|
|
+
|
|
|
+ // 关键:先把旧窗口销毁、把 manager 解绑,再让 DialogBox 创建新的对话框窗口,
|
|
|
+ // 这样 manager 不会出现"两个 HWND 同时活着"的错乱状态。
|
|
|
+ // 说明:调用方是在 ShowModal() 之前 Create 的,所以这里必须主动清理旧窗口。
|
|
|
+ HWND hWndOldCaptured = m_hWnd;
|
|
|
+ m_hWnd = NULL; // 先置空:__DialogProc 会在 WM_INITDIALOG 里填入新句柄
|
|
|
+
|
|
|
+ // 把 manager 从旧窗口上摘下来(保留它的控件树/资源,只换 paint window)
|
|
|
+ CPaintManagerUI* pManager = FindManagerByPaintWindow(hWndOldCaptured);
|
|
|
+ SetDetachedManager(pManager);
|
|
|
+ if( pManager != NULL )
|
|
|
+ {
|
|
|
+ pManager->DetachPaintWindow();
|
|
|
+ DuiLib::TouchTrace::Log(_T("MODAL-DLG detach manager=%p from old=%p"),
|
|
|
+ (void*)pManager, (void*)hWndOldCaptured);
|
|
|
+ }
|
|
|
+ // 销毁旧窗口(旧窗口上的窗口过程会把 m_hWnd 清空,但我们已经在上面置 NULL 了,
|
|
|
+ // 且 manager 已摘除,不会再触发 DuiLib 的绘制路径)。
|
|
|
+ if( ::IsWindow(hWndOldCaptured) ) ::DestroyWindow(hWndOldCaptured);
|
|
|
+
|
|
|
+ // ---- 4) 交给系统对话框管理器跑模态循环 ----
|
|
|
+ // 这是阻塞调用,返回时模态已结束。
|
|
|
+ INT_PTR nRet = ::DialogBoxIndirectParam(CPaintManagerUI::GetInstance(),
|
|
|
+ pTemplate,
|
|
|
+ hWndOwner,
|
|
|
+ CWindowWnd::__DialogProc,
|
|
|
+ (LPARAM)this);
|
|
|
+
|
|
|
+ // 资源可以释放了(DialogBoxIndirectParam 内部已把模板复制走)——
|
|
|
+ // 但谨慎起见放在 EndDialog 之后,与 nim_duilib 一致。
|
|
|
+ ::GlobalUnlock(hResource);
|
|
|
+ ::GlobalFree(hResource);
|
|
|
+
|
|
|
+ // 兜底:若 __DialogProc 因故没跑(创建失败 / WM_INITDIALOG 里 early-return),
|
|
|
+ // 这里把中转变量清干净,避免悬垂指针影响后续 ShowModal。
|
|
|
+ SetDetachedManager(NULL);
|
|
|
+
|
|
|
+ m_bDoModal = false;
|
|
|
+ CPaintManagerUI::SetModalWindow(hPrevModal);
|
|
|
+
|
|
|
+ DuiLib::TouchTrace::Log(_T("MODAL-DLG exit hwnd=%p ret=%d m_nModalRet=%u"),
|
|
|
+ (void*)m_hWnd, (int)nRet, m_nModalRet);
|
|
|
+
|
|
|
+ // DialogBoxIndirectParam 返回值:-1 表示创建失败
|
|
|
+ if( nRet == -1 )
|
|
|
+ {
|
|
|
+ DuiLib::TouchTrace::Log(_T("MODAL-DLG failed err=%u"), ::GetLastError());
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ return m_nModalRet;
|
|
|
+}
|
|
|
+
|
|
|
+// -----------------------------------------------------------------------------
|
|
|
+// 真模态:对话框过程。
|
|
|
+//
|
|
|
+// 生命周期:
|
|
|
+// DialogBoxIndirectParam → 创建 #32770 窗口 → WM_INITDIALOG(此时才有真 HWND)
|
|
|
+// → 把 manager 挂到新 HWND、换掉 GWLP_WNDPROC
|
|
|
+// → return TRUE(表示我们已经处理了焦点)
|
|
|
+// -----------------------------------------------------------------------------
|
|
|
+INT_PTR CALLBACK CWindowWnd::__DialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
|
+{
|
|
|
+ if( uMsg == WM_INITDIALOG )
|
|
|
+ {
|
|
|
+ // 这是对话框的第一个消息;lParam 是我们传进来的 this
|
|
|
+ CWindowWnd* pThis = reinterpret_cast<CWindowWnd*>(lParam);
|
|
|
+ if( pThis == NULL ) return TRUE;
|
|
|
+
|
|
|
+ ASSERT(pThis->m_hWnd == NULL);
|
|
|
+ pThis->m_hWnd = hDlg;
|
|
|
+
|
|
|
+ // ---- 换掉对话框默认的窗口过程 ----
|
|
|
+ // 这样后续所有消息(含 WM_PAINT / WM_TOUCH / 鼠标)都会走到
|
|
|
+ // CWindowWnd::HandleMessage → CPaintManagerUI,DuiLib 的自绘与路由完全保留。
|
|
|
+ ASSERT(pThis->m_pfnOldDlgWndProc == NULL);
|
|
|
+ pThis->m_pfnOldDlgWndProc = (WNDPROC)::GetWindowLongPtr(hDlg, GWLP_WNDPROC);
|
|
|
+ ::SetWindowLongPtr(hDlg, GWLP_WNDPROC, (LONG_PTR)(WNDPROC)CWindowWnd::__DialogWndProc);
|
|
|
+ ::SetWindowLongPtr(hDlg, GWLP_USERDATA, (LONG_PTR)pThis);
|
|
|
+
|
|
|
+ // ---- 登记为当前模态窗口 ----
|
|
|
+ CPaintManagerUI::SetModalWindow(hDlg);
|
|
|
+
|
|
|
+ // ---- 触摸注册:对话框是全新的 HWND,必须注册,否则收不到 WM_TOUCH ----
|
|
|
+ RegisterTouchWindowIfAvailable(hDlg);
|
|
|
+
|
|
|
+ DuiLib::TouchTrace::Log(_T("MODAL-DLG init dlg=%p oldwndproc=%p focus=%p"),
|
|
|
+ (void*)hDlg, (void*)pThis->m_pfnOldDlgWndProc, (void*)::GetFocus());
|
|
|
+
|
|
|
+ // ---- 把旧的 CPaintManagerUI 挂到对话框窗口上 ----
|
|
|
+ // 调用方在 ShowModal() 之前已 Create + manager.Init 到旧 HWND;这里把 manager 迁移过来,
|
|
|
+ // 让 DuiLib 的全部既有逻辑(绘制 / 消息 / 控件树)在新窗口上继续工作。
|
|
|
+ CPaintManagerUI* pManager = GetDetachedManager();
|
|
|
+ if( pManager != NULL )
|
|
|
+ {
|
|
|
+ pManager->AttachPaintWindow(hDlg);
|
|
|
+ DuiLib::TouchTrace::Log(_T("MODAL-DLG attach manager=%p to dlg=%p"), (void*)pManager, (void*)hDlg);
|
|
|
+ // 用掉即清:避免这个文件级静态变量残留一个已失效的指针,
|
|
|
+ // 影响下一次 ShowModal(尤其嵌套弹框:A 弹框里再开 B 弹框)。
|
|
|
+ SetDetachedManager(NULL);
|
|
|
+ }
|
|
|
+ else
|
|
|
{
|
|
|
- continue;
|
|
|
+ DuiLib::TouchTrace::Log(_T("MODAL-DLG no detached manager (dialog will Init itself)"));
|
|
|
}
|
|
|
|
|
|
- if( msg.message == WM_CLOSE && msg.hwnd == m_hWnd ) {
|
|
|
- nRet = (int)msg.wParam;
|
|
|
- ::SetFocus(hWndParent);
|
|
|
+ // ---- [WM_TOUCH 归属权] 立刻把"补调原窗口过程"的钩子绑到本弹框 ----
|
|
|
+ // 为什么必须在这里(而不是等 __DialogWndProc 首次进入时再绑):
|
|
|
+ // __DialogProc 在 WM_INITDIALOG 里直接调 pThis->HandleMessage(见下方),
|
|
|
+ // 这条路径**不经过** __DialogWndProc,所以"首次进入窗口过程时绑定"
|
|
|
+ // 在这条路上不会触发。而 AttachPaintWindow 之后 manager 已经指向 hDlg,
|
|
|
+ // 一旦有 WM_MOUSEMOVE 到达就可能需要补调 ⇒ 必须现在就绑好。
|
|
|
+ // 绑定对象是 m_pfnOldDlgWndProc(即 #32770 原过程 / DefDlgProc)。
|
|
|
+ if( pManager != NULL )
|
|
|
+ {
|
|
|
+ // m_pfnOldDlgWndProc 本身就是 WNDPROC;只有它意外为空时才回落到 DefDlgProc
|
|
|
+ // (签名是 INT_PTR 返回,需显式转换,不能与 WNDPROC 混在三目里)。
|
|
|
+ WNDPROC pfnDlgTarget = pThis->m_pfnOldDlgWndProc;
|
|
|
+ if( pfnDlgTarget == NULL )
|
|
|
+ pfnDlgTarget = reinterpret_cast<WNDPROC>(::DefDlgProc);
|
|
|
+ BindRelayTarget(pfnDlgTarget, hDlg);
|
|
|
+ pManager->SetDefProcRelay(&RelayWndProc, hDlg);
|
|
|
}
|
|
|
|
|
|
- // Win10 前台锁比 Win7 严格:弹框偶尔会丢掉前台/激活状态,之后触摸就会被路由到别处。
|
|
|
- // 这里做限流补偿(≥800ms 一次,且【绝不在手势消息期间】执行,避免打断输入路由)。
|
|
|
- if( !IsModalBlockedInputMessage(msg.message) )
|
|
|
+ // ---- 尺寸校正(关键)----
|
|
|
+ // DLGTEMPLATE 里给的 cx/cy 是对话框单位(DLU)、且语义为「客户区」,系统创建时还会
|
|
|
+ // 按实际样式额外加一圈非客户区边框。所以不能指望"模板尺寸 == 目标尺寸"。
|
|
|
+ // 这里用 AdjustWindowRectEx 按对话框**真实**的样式反算目标外框,再 SetWindowPos 精确校正,
|
|
|
+ // 保证客户区尺寸与原窗口逐像素一致 —— 这正是"弹框变大了"的修法。
|
|
|
+ //
|
|
|
+ // 放在 AttachPaintWindow 之后、HandleMessage(WM_INITDIALOG) 之前:
|
|
|
+ // 此时 manager 已绑定新 HWND,尺寸变化会走正常的布局流程;且业务层 OnCreate 之前
|
|
|
+ // 窗口就已经是最终尺寸,避免"先小后大"的闪动与一次性布局错位。
|
|
|
{
|
|
|
- const DWORD dwNow = ::GetTickCount();
|
|
|
- if( (LONG)(dwNow - s_dwLastModalFocusFix) >= 800
|
|
|
- && (::GetActiveWindow() != m_hWnd || ::GetForegroundWindow() != m_hWnd) )
|
|
|
- {
|
|
|
- s_dwLastModalFocusFix = dwNow;
|
|
|
- DuiLib::TouchTrace::Log(_T("MODAL-REACTIVATE hwnd=%p active=%p foreground=%p"),
|
|
|
- (void*)m_hWnd, (void*)::GetActiveWindow(), (void*)::GetForegroundWindow());
|
|
|
- ::BringWindowToTop(m_hWnd);
|
|
|
- ::SetForegroundWindow(m_hWnd);
|
|
|
- ::SetActiveWindow(m_hWnd);
|
|
|
- ::SetFocus(m_hWnd);
|
|
|
- }
|
|
|
+ const DWORD dwRealStyle = (DWORD)::GetWindowLong(hDlg, GWL_STYLE);
|
|
|
+ const DWORD dwRealExStyle = (DWORD)::GetWindowLong(hDlg, GWL_EXSTYLE);
|
|
|
+ RECT rcAdjust = { 0, 0,
|
|
|
+ pThis->m_rcModalTarget.right - pThis->m_rcModalTarget.left,
|
|
|
+ pThis->m_rcModalTarget.bottom - pThis->m_rcModalTarget.top };
|
|
|
+ const int nClientW = rcAdjust.right;
|
|
|
+ const int nClientH = rcAdjust.bottom;
|
|
|
+ // 反算:以"目标客户区尺寸"为输入,得到需要的外框尺寸
|
|
|
+ ::AdjustWindowRectEx(&rcAdjust, dwRealStyle, FALSE, dwRealExStyle);
|
|
|
+
|
|
|
+ const int nWndW = rcAdjust.right - rcAdjust.left;
|
|
|
+ const int nWndH = rcAdjust.bottom - rcAdjust.top;
|
|
|
+
|
|
|
+ RECT rcCur = { 0 };
|
|
|
+ ::GetWindowRect(hDlg, &rcCur);
|
|
|
+ DuiLib::TouchTrace::Log(_T("MODAL-DLG resize before cur=%dx%d clientTarget=%dx%d -> wnd=%dx%d hide=%d"),
|
|
|
+ rcCur.right - rcCur.left, rcCur.bottom - rcCur.top,
|
|
|
+ nClientW, nClientH, nWndW, nWndH,
|
|
|
+ (int)((dwRealStyle & WS_VISIBLE) == 0));
|
|
|
+
|
|
|
+ // SWP_FRAMECHANGED 让系统重新计算非客户区(样式刚被 SetWindowLongPtr 换过)
|
|
|
+ ::SetWindowPos(hDlg, NULL,
|
|
|
+ pThis->m_rcModalTarget.left, pThis->m_rcModalTarget.top,
|
|
|
+ nWndW, nWndH,
|
|
|
+ SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
|
|
|
+
|
|
|
+ RECT rcAfter = { 0 };
|
|
|
+ ::GetClientRect(hDlg, &rcAfter);
|
|
|
+ DuiLib::TouchTrace::Log(_T("MODAL-DLG resize after client=%dx%d (target=%dx%d)"),
|
|
|
+ rcAfter.right - rcAfter.left, rcAfter.bottom - rcAfter.top, nClientW, nClientH);
|
|
|
}
|
|
|
|
|
|
- if( !CPaintManagerUI::TranslateMessage(&msg) ) {
|
|
|
- ::TranslateMessage(&msg);
|
|
|
- ::DispatchMessage(&msg);
|
|
|
+ // 交给 CWindowWnd::HandleMessage 处理 WM_INITDIALOG,业务层的 OnCreate 等由此触发。
|
|
|
+ pThis->HandleMessage(uMsg, wParam, lParam);
|
|
|
+
|
|
|
+ ::SetFocus(hDlg);
|
|
|
+ return TRUE; // TRUE = 我们自己设置了焦点
|
|
|
+ }
|
|
|
+
|
|
|
+ return FALSE;
|
|
|
+}
|
|
|
+
|
|
|
+// -----------------------------------------------------------------------------
|
|
|
+// 真模态:对话框窗口的窗口过程。
|
|
|
+// 几乎就是 __WndProc 的对话框版本。
|
|
|
+// -----------------------------------------------------------------------------
|
|
|
+LRESULT CALLBACK CWindowWnd::__DialogWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
|
+{
|
|
|
+ CWindowWnd* pThis = reinterpret_cast<CWindowWnd*>(::GetWindowLongPtr(hWnd, GWLP_USERDATA));
|
|
|
+
|
|
|
+ if( uMsg == WM_NCDESTROY && pThis != NULL )
|
|
|
+ {
|
|
|
+ // 顺序很关键:
|
|
|
+ // 1) 先把窗口过程还原(此后系统调用会回到对话框默认过程);
|
|
|
+ // 2) 再让 DefWindowProc 做标准清理;
|
|
|
+ // 3) 清 GWLP_USERDATA —— 必须在 HandleMessage 之前,防止重入时又拿到 this;
|
|
|
+ // 4) 清模态登记;
|
|
|
+ // 5) m_hWnd 置空【要在 Unsubclass 之前】—— 注意本项目弹框并不 Subclass,
|
|
|
+ // m_bSubclassed 恒为 false,这里只是保持与 __WndProc 一致的写法,
|
|
|
+ // 并让 OnFinalMessage 里看到的 m_hWnd 为空(与旧行为一致)。
|
|
|
+ if( pThis->m_pfnOldDlgWndProc != NULL )
|
|
|
+ {
|
|
|
+ ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)pThis->m_pfnOldDlgWndProc);
|
|
|
+ pThis->m_pfnOldDlgWndProc = NULL;
|
|
|
+ }
|
|
|
+ LRESULT lRes = ::DefWindowProc(hWnd, uMsg, wParam, lParam);
|
|
|
+ // [WM_TOUCH 归属权] 解绑转发目标,避免悬垂到已销毁的对话框窗口过程。
|
|
|
+ {
|
|
|
+ DuiLib::CPaintManagerUI* pMgrNc = FindManagerByPaintWindow(hWnd);
|
|
|
+ if( pMgrNc != NULL ) pMgrNc->ClearDefProcRelay(hWnd);
|
|
|
+ BindRelayTarget(NULL, NULL);
|
|
|
}
|
|
|
+ ::SetWindowLongPtr(hWnd, GWLP_USERDATA, 0L);
|
|
|
+ CPaintManagerUI::SetModalWindow(NULL);
|
|
|
+ pThis->m_hWnd = NULL;
|
|
|
+ if( pThis->m_bSubclassed ) pThis->Unsubclass();
|
|
|
+ pThis->OnFinalMessage(hWnd);
|
|
|
+ return lRes;
|
|
|
}
|
|
|
|
|
|
- CPaintManagerUI::SetModalWindow(hPrevModal);
|
|
|
- if( ::IsWindow(m_hWnd) )
|
|
|
- ::SetWindowPos(m_hWnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
|
|
- if( hWndParent != NULL ) {
|
|
|
- if( ::IsWindow(hWndParent) ) {
|
|
|
- ::EnableWindow(hWndParent, TRUE);
|
|
|
- ::SetForegroundWindow(hWndParent);
|
|
|
- ::SetFocus(hWndParent);
|
|
|
+ // WM_INITDIALOG 已经由 __DialogProc 处理并 return TRUE,
|
|
|
+ // 一般不会再走到这里;若系统仍投递(例如 EndDialog 之后的收尾路径),
|
|
|
+ // 直接交回对话框默认过程,避免被 HandleMessage 当成普通消息误处理。
|
|
|
+ if( uMsg == WM_INITDIALOG )
|
|
|
+ return ::DefDlgProc(hWnd, uMsg, wParam, lParam);
|
|
|
+
|
|
|
+ // 诊断:确认 WM_TOUCH 到底投递给了哪个窗口。
|
|
|
+ if( uMsg == WM_TOUCH ) TraceTouchArrival(hWnd, wParam, lParam);
|
|
|
+ if( pThis != NULL ) TraceInputMessage(hWnd, uMsg, wParam, lParam);
|
|
|
+
|
|
|
+ if( pThis != NULL )
|
|
|
+ {
|
|
|
+ // ---------------------------------------------------------------
|
|
|
+ // [WM_TOUCH 归属权] 注册"补调原窗口过程"的钩子。
|
|
|
+ //
|
|
|
+ // 真模态弹框的窗口类是系统对话框类 #32770,它把"把特殊标记的
|
|
|
+ // WM_MOUSEMOVE 翻译成 WM_TOUCH"这件事放在 DefDlgProc → DefWindowProc 里做。
|
|
|
+ // 而 __DialogWndProc 一旦 pThis != NULL 就全部交给 HandleMessage,
|
|
|
+ // 再也不回落 DefDlgProc ⇒ 弹框永远收不到 WM_TOUCH(与实测吻合)。
|
|
|
+ //
|
|
|
+ // 这里登记的原过程是 m_pfnOldDlgWndProc(也就是 DefDlgProc),
|
|
|
+ // 使 manager 在吞掉触摸提升鼠标消息时能补调它一次,把翻译链接上。
|
|
|
+ // ---------------------------------------------------------------
|
|
|
+ // ⚠️ 判据必须是"当前绑定的窗口不是本窗口",不能写 `GetDefProcRelay() == NULL`:
|
|
|
+ // 真模态弹框复用的是**同一个** manager 对象(从旧窗口 Detach、再 Attach 到弹框),
|
|
|
+ // 主窗口早已注册过钩子 ⇒ 按 NULL 判断则弹框永远不重新绑定,
|
|
|
+ // 转发目标会一直指向主窗口的原过程,补调无效甚至喂错窗口。
|
|
|
+ DuiLib::CPaintManagerUI* pMgr = FindManagerByPaintWindow(hWnd);
|
|
|
+ if( pMgr != NULL && pMgr->GetDefProcRelayBoundWindow() != hWnd )
|
|
|
+ {
|
|
|
+ // ⚠️ DefDlgProc 的签名是 `INT_PTR (CALLBACK*)(...)`,与 WNDPROC
|
|
|
+ // (`LRESULT (CALLBACK*)(...)`)返回类型不同,三目运算符会类型不匹配。
|
|
|
+ // 用一个显式分支赋值,避免 C2446。
|
|
|
+ WNDPROC pfnTarget = NULL;
|
|
|
+ if( pThis->m_pfnOldDlgWndProc != NULL )
|
|
|
+ pfnTarget = pThis->m_pfnOldDlgWndProc;
|
|
|
+ else
|
|
|
+ pfnTarget = reinterpret_cast<WNDPROC>(::DefDlgProc);
|
|
|
+ BindRelayTarget(pfnTarget, hWnd);
|
|
|
+ pMgr->SetDefProcRelay(&RelayWndProc, hWnd);
|
|
|
}
|
|
|
+ return pThis->HandleMessage(uMsg, wParam, lParam);
|
|
|
}
|
|
|
- DuiLib::TouchTrace::Log(_T("MODAL-EXIT hwnd=%p owner=%p ret=%u"), (void*)m_hWnd, (void*)hWndParent, nRet);
|
|
|
- if( msg.message == WM_QUIT ) ::PostQuitMessage((int)msg.wParam);
|
|
|
- return nRet;
|
|
|
+
|
|
|
+ return ::DefDlgProc(hWnd, uMsg, wParam, lParam);
|
|
|
}
|
|
|
|
|
|
+
|
|
|
void CWindowWnd::ShowModalFake()
|
|
|
{
|
|
|
ASSERT(::IsWindow(m_hWnd));
|
|
|
@@ -509,6 +975,24 @@ void CWindowWnd::Close(UINT nRet)
|
|
|
|
|
|
ASSERT(::IsWindow(m_hWnd));
|
|
|
if (!::IsWindow(m_hWnd)) return;
|
|
|
+
|
|
|
+ // -------------------------------------------------------------------------
|
|
|
+ // 真模态:走 EndDialog 结束系统对话框循环。
|
|
|
+ //
|
|
|
+ // 旧的假模态是 PostMessage(WM_CLOSE) 让自写循环退出;真模态下必须用 EndDialog,
|
|
|
+ // 否则 DialogBoxIndirectParam 不会返回。返回值写入 m_nModalRet,由 ShowModal 读走。
|
|
|
+ //
|
|
|
+ // 注意:EndDialog 会销毁对话框窗口 → 触发 WM_NCDESTROY → __DialogWndProc 收尾
|
|
|
+ // (还原窗口过程、清 USERDATA、调 OnFinalMessage)。所以这里不要再 DestroyWindow。
|
|
|
+ // -------------------------------------------------------------------------
|
|
|
+ if (m_bDoModal)
|
|
|
+ {
|
|
|
+ m_nModalRet = nRet;
|
|
|
+ DuiLib::TouchTrace::Log(_T("MODAL-DLG EndDialog hwnd=%p ret=%u"), (void*)m_hWnd, nRet);
|
|
|
+ ::EndDialog(m_hWnd, (INT_PTR)nRet);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
PostMessage(WM_CLOSE, (WPARAM)nRet, 0L);
|
|
|
}
|
|
|
|
|
|
@@ -639,9 +1123,11 @@ bool CWindowWnd::RegisterSuperclass()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// 注:RelayWndProc / BindRelayTarget 已上移到文件前部(FindManagerByPaintWindow 之后),
|
|
|
+// 因为 __DialogWndProc 比本函数先出现、也要用到它们。
|
|
|
+
|
|
|
LRESULT CALLBACK CWindowWnd::__WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
|
-{
|
|
|
- CWindowWnd* pThis = NULL;
|
|
|
+{ CWindowWnd* pThis = NULL;
|
|
|
if( uMsg == WM_NCCREATE ) {
|
|
|
LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam);
|
|
|
pThis = static_cast<CWindowWnd*>(lpcs->lpCreateParams);
|
|
|
@@ -652,6 +1138,10 @@ LRESULT CALLBACK CWindowWnd::__WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPAR
|
|
|
pThis = reinterpret_cast<CWindowWnd*>(::GetWindowLongPtr(hWnd, GWLP_USERDATA));
|
|
|
if( uMsg == WM_NCDESTROY && pThis != NULL ) {
|
|
|
LRESULT lRes = ::CallWindowProc(pThis->m_OldWndProc, hWnd, uMsg, wParam, lParam);
|
|
|
+ // [WM_TOUCH 归属权] 解绑转发目标,避免悬垂到已销毁窗口的窗口过程。
|
|
|
+ DuiLib::CPaintManagerUI* pMgrNc = FindManagerByPaintWindow(hWnd);
|
|
|
+ if( pMgrNc != NULL ) pMgrNc->ClearDefProcRelay(hWnd);
|
|
|
+ BindRelayTarget(NULL, NULL);
|
|
|
::SetWindowLongPtr(pThis->m_hWnd, GWLP_USERDATA, 0L);
|
|
|
if( pThis->m_bSubclassed ) pThis->Unsubclass();
|
|
|
pThis->m_hWnd = NULL;
|
|
|
@@ -662,7 +1152,27 @@ LRESULT CALLBACK CWindowWnd::__WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPAR
|
|
|
// 诊断:确认 WM_TOUCH 到底投递给了哪个窗口(弹框 / owner / 其它)。
|
|
|
if( uMsg == WM_TOUCH ) TraceTouchArrival(hWnd, wParam, lParam);
|
|
|
|
|
|
+ // 【临时全量诊断】所有输入类消息(含 WM_TOUCH / WM_GESTURE / WM_POINTER* / 鼠标 / 激活)
|
|
|
+ // 都记一条,用来判断系统到底把消息发给了谁。
|
|
|
+ if( pThis != NULL ) TraceInputMessage(hWnd, uMsg, wParam, lParam);
|
|
|
+
|
|
|
if( pThis != NULL ) {
|
|
|
+ // ---------------------------------------------------------------
|
|
|
+ // [WM_TOUCH 归属权] 注册"补调原窗口过程"的钩子。
|
|
|
+ // 把 m_OldWndProc(本窗口类的原过程)登记为转发目标,并交给 manager。
|
|
|
+ // manager 在吞掉"触摸提升 WM_MOUSEMOVE"时会回调它一次,
|
|
|
+ // 让系统的 WM_TOUCH 翻译链不断掉。
|
|
|
+ //
|
|
|
+ // 判据用"绑定窗口 != 本窗口"而非"钩子为空":真模态弹框复用同一个
|
|
|
+ // manager,换 HWND 时必须能重新绑定到对话框架自己的原过程。
|
|
|
+ // 开销:仅在窗口首个消息时走一遍查找,之后靠 manager 内的指针直连。
|
|
|
+ // ---------------------------------------------------------------
|
|
|
+ DuiLib::CPaintManagerUI* pMgr = FindManagerByPaintWindow(hWnd);
|
|
|
+ if( pMgr != NULL && pMgr->GetDefProcRelayBoundWindow() != hWnd )
|
|
|
+ {
|
|
|
+ BindRelayTarget(pThis->m_OldWndProc, hWnd);
|
|
|
+ pMgr->SetDefProcRelay(&RelayWndProc, hWnd);
|
|
|
+ }
|
|
|
return pThis->HandleMessage(uMsg, wParam, lParam);
|
|
|
}
|
|
|
else {
|