|
|
@@ -1,4 +1,5 @@
|
|
|
#include "StdAfx.h"
|
|
|
+#include "UIManager.h"
|
|
|
|
|
|
#ifdef _DEBUG
|
|
|
#include <shlwapi.h>
|
|
|
@@ -7,9 +8,34 @@
|
|
|
|
|
|
namespace DuiLib {
|
|
|
|
|
|
+// WM_TOUCH 消息 ID 在 winuser.h 里受 `#if _WIN32_WINNT >= 0x0601` 保护,
|
|
|
+// 而 DuiLib 工程的 _WIN32_WINNT 兜底为 XP(0x0501),直接引用会编译失败,这里自行声明。
|
|
|
+#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
|
|
|
+
|
|
|
namespace {
|
|
|
typedef BOOL (WINAPI* PFN_REGISTER_TOUCH_WINDOW)(HWND, ULONG);
|
|
|
|
|
|
+ // 与 SDK tagTOUCHINPUT 布局一致的私有副本(同样受 _WIN32_WINNT 保护,不能直接用)
|
|
|
+ struct TRACE_TOUCHINPUT
|
|
|
+ {
|
|
|
+ LONG x;
|
|
|
+ LONG y;
|
|
|
+ HANDLE hSource;
|
|
|
+ DWORD dwID;
|
|
|
+ DWORD dwFlags;
|
|
|
+ DWORD dwMask;
|
|
|
+ DWORD dwTime;
|
|
|
+ ULONG_PTR dwExtraInfo;
|
|
|
+ DWORD cxContact;
|
|
|
+ DWORD cyContact;
|
|
|
+ };
|
|
|
+ typedef BOOL (WINAPI* PFN_GET_TOUCH_INPUT_INFO)(HANDLE, UINT, TRACE_TOUCHINPUT*, int);
|
|
|
+
|
|
|
void RegisterTouchWindowIfAvailable(HWND hWnd)
|
|
|
{
|
|
|
if (hWnd == NULL) return;
|
|
|
@@ -20,6 +46,47 @@ namespace {
|
|
|
if (pfnRegister != NULL)
|
|
|
pfnRegister(hWnd, 0);
|
|
|
}
|
|
|
+
|
|
|
+ // 诊断:WM_TOUCH 究竟落在哪个窗口上(弹框自己?还是被禁用/被挡住的 owner?)。
|
|
|
+ // ⚠️ 只 peek,绝不调用 CloseTouchInputHandle:句柄必须交给 CPaintManagerUI::OnTouchInput 关闭。
|
|
|
+ void TraceTouchArrival(HWND hWnd, WPARAM wParam, LPARAM lParam)
|
|
|
+ {
|
|
|
+ HMODULE hUser32 = ::GetModuleHandle(_T("user32.dll"));
|
|
|
+ if (hUser32 == NULL) return;
|
|
|
+ PFN_GET_TOUCH_INPUT_INFO pfnGet =
|
|
|
+ reinterpret_cast<PFN_GET_TOUCH_INPUT_INFO>(::GetProcAddress(hUser32, "GetTouchInputInfo"));
|
|
|
+ int x = 0, y = 0;
|
|
|
+ DWORD flags = 0;
|
|
|
+ UINT cInputs = LOWORD(wParam);
|
|
|
+ TRACE_TOUCHINPUT ti;
|
|
|
+ ::ZeroMemory(&ti, sizeof(ti));
|
|
|
+ if (pfnGet != NULL && cInputs > 0 && pfnGet((HANDLE)lParam, 1, &ti, (int)sizeof(ti)))
|
|
|
+ {
|
|
|
+ x = (int)(ti.x / 100);
|
|
|
+ y = (int)(ti.y / 100);
|
|
|
+ flags = ti.dwFlags;
|
|
|
+ }
|
|
|
+ DuiLib::TouchTrace::Log(_T("WNDPROC-TOUCH hwnd=%p owner=%p parent=%p enabled=%d visible=%d pt=%d,%d flags=0x%X inputs=%u modal=%p fg=%p"),
|
|
|
+ (void*)hWnd, (void*)::GetWindow(hWnd, GW_OWNER), (void*)::GetParent(hWnd),
|
|
|
+ (int)::IsWindowEnabled(hWnd), (int)::IsWindowVisible(hWnd),
|
|
|
+ x, y, (unsigned)flags, cInputs,
|
|
|
+ (void*)DuiLib::CPaintManagerUI::GetModalWindow(), (void*)::GetForegroundWindow());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 上一次"抢回前台"的时间戳(限流用,避免频繁改激活状态打断输入)
|
|
|
+ DWORD s_dwLastModalFocusFix = 0;
|
|
|
+
|
|
|
+ // 模态期间需要屏蔽的输入类消息(发给被弹框挡住的 owner 时直接丢弃,不再派发)
|
|
|
+ bool IsModalBlockedInputMessage(UINT uMsg)
|
|
|
+ {
|
|
|
+ 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;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////
|
|
|
@@ -326,24 +393,94 @@ UINT CWindowWnd::ShowModal()
|
|
|
ASSERT(::IsWindow(m_hWnd));
|
|
|
UINT nRet = 0;
|
|
|
HWND hWndParent = GetWindowOwner(m_hWnd);
|
|
|
+ const bool bBlockParent = (hWndParent != NULL && ::IsWindow(hWndParent));
|
|
|
+
|
|
|
RegisterTouchWindowIfAvailable(m_hWnd);
|
|
|
+
|
|
|
+ // -------------------------------------------------------------------------
|
|
|
+ // 【重要】这里不再调用 ::EnableWindow(hWndParent, FALSE)。
|
|
|
+ //
|
|
|
+ // 原因:Windows 8/10 把触摸输入改成了 Pointer 栈,触摸消息(WM_TOUCH / WM_POINTER*)
|
|
|
+ // 的投递目标与"窗口是否被禁用 / 是否是活动窗口"强相关。实测在 Win10 上,只要 owner
|
|
|
+ // 被 EnableWindow(FALSE) 禁用,手势就会被系统转投给被禁用的 owner 而不再到达弹框,
|
|
|
+ // 于是弹框既收不到 WM_TOUCH(滚动条无法触摸滑动)、也没有可靠的提升鼠标消息可用
|
|
|
+ // (点击无反应);而在 Win7 上鼠标/触摸提升路径不受此影响,所以旧写法一直正常。
|
|
|
+ //
|
|
|
+ // 模态性改用两条等价手段保证:
|
|
|
+ // ① 弹框置顶 + 激活,抢到前台;
|
|
|
+ // ② 消息循环里丢弃所有发给 owner(及其子窗口)的输入类消息(见 IsModalBlockedInputMessage)。
|
|
|
+ // —— owner 保持 enabled,它的鼠标/键盘/触摸都不会被派发,等价于"点不动";
|
|
|
+ // ③ CPaintManagerUI 层再加一道:有模态弹框时,非弹框窗口收到 WM_TOUCH 直接忽略。
|
|
|
+ // -------------------------------------------------------------------------
|
|
|
::ShowWindow(m_hWnd, SW_SHOWNORMAL);
|
|
|
- ::EnableWindow(hWndParent, FALSE);
|
|
|
+ ::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);
|
|
|
+
|
|
|
+ // 支持弹框嵌套:保存外层模态窗口,退出时恢复
|
|
|
+ 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) )
|
|
|
+ {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
if( msg.message == WM_CLOSE && msg.hwnd == m_hWnd ) {
|
|
|
nRet = (int)msg.wParam;
|
|
|
- ::EnableWindow(hWndParent, TRUE);
|
|
|
::SetFocus(hWndParent);
|
|
|
}
|
|
|
+
|
|
|
+ // Win10 前台锁比 Win7 严格:弹框偶尔会丢掉前台/激活状态,之后触摸就会被路由到别处。
|
|
|
+ // 这里做限流补偿(≥800ms 一次,且【绝不在手势消息期间】执行,避免打断输入路由)。
|
|
|
+ if( !IsModalBlockedInputMessage(msg.message) )
|
|
|
+ {
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
if( !CPaintManagerUI::TranslateMessage(&msg) ) {
|
|
|
::TranslateMessage(&msg);
|
|
|
::DispatchMessage(&msg);
|
|
|
}
|
|
|
- if( msg.message == WM_QUIT ) break;
|
|
|
}
|
|
|
- ::EnableWindow(hWndParent, TRUE);
|
|
|
- ::SetFocus(hWndParent);
|
|
|
+
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ 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;
|
|
|
}
|
|
|
@@ -522,6 +659,9 @@ LRESULT CALLBACK CWindowWnd::__WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPAR
|
|
|
return lRes;
|
|
|
}
|
|
|
}
|
|
|
+ // 诊断:确认 WM_TOUCH 到底投递给了哪个窗口(弹框 / owner / 其它)。
|
|
|
+ if( uMsg == WM_TOUCH ) TraceTouchArrival(hWnd, wParam, lParam);
|
|
|
+
|
|
|
if( pThis != NULL ) {
|
|
|
return pThis->HandleMessage(uMsg, wParam, lParam);
|
|
|
}
|