|
|
@@ -1,5 +1,9 @@
|
|
|
#include "StdAfx.h"
|
|
|
#include <zmouse.h>
|
|
|
+// [触屏诊断] 显式引入,避免依赖 tchar.h 的传递包含
|
|
|
+#include <stdio.h>
|
|
|
+#include <stdarg.h>
|
|
|
+#include <tchar.h>
|
|
|
|
|
|
#pragma warning(push)
|
|
|
#pragma warning(disable:4838 4244)
|
|
|
@@ -30,6 +34,72 @@ namespace DuiLib
|
|
|
#define WM_TOUCH 0x0240
|
|
|
#endif
|
|
|
|
|
|
+ // ---------------------------------------------------------------------------
|
|
|
+ // [触屏诊断] 临时打点:DuiLib 自身无日志库,故用无依赖的追加文件法。
|
|
|
+ // 输出到 exe 同目录 touch_trace.log(每行带 tick/线程)。定位完成后可整段删除。
|
|
|
+ // 开关:环境变量 ZP_TOUCH_TRACE=0 可关闭;默认开启。
|
|
|
+ // ---------------------------------------------------------------------------
|
|
|
+ namespace TouchTrace
|
|
|
+ {
|
|
|
+ inline bool Enabled()
|
|
|
+ {
|
|
|
+ static int s_state = -1;
|
|
|
+ if (s_state < 0)
|
|
|
+ {
|
|
|
+ TCHAR buf[8] = { 0 };
|
|
|
+ DWORD n = ::GetEnvironmentVariable(_T("ZP_TOUCH_TRACE"), buf, 8);
|
|
|
+ s_state = (n > 0 && buf[0] == _T('0')) ? 0 : 1;
|
|
|
+ }
|
|
|
+ return s_state == 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 极简 TCHAR* → UTF-8 char* 转换(仅用于日志路径;非线程安全,够用即可)
|
|
|
+ inline const char* Narrow(LPCTSTR s)
|
|
|
+ {
|
|
|
+ static char s_buf[MAX_PATH * 3] = { 0 };
|
|
|
+#ifdef _UNICODE
|
|
|
+ s_buf[0] = '\0';
|
|
|
+ if (s != NULL)
|
|
|
+ ::WideCharToMultiByte(CP_UTF8, 0, s, -1, s_buf, (int)sizeof(s_buf) - 1, NULL, NULL);
|
|
|
+ return s_buf;
|
|
|
+#else
|
|
|
+ return s;
|
|
|
+#endif
|
|
|
+ }
|
|
|
+
|
|
|
+ inline void Log(LPCTSTR fmt, ...)
|
|
|
+ {
|
|
|
+ if (!Enabled()) return;
|
|
|
+
|
|
|
+ TCHAR path[MAX_PATH] = { 0 };
|
|
|
+ ::GetModuleFileName(NULL, path, MAX_PATH);
|
|
|
+ TCHAR* p = _tcsrchr(path, _T('\\'));
|
|
|
+ if (p != NULL) *(p + 1) = _T('\0');
|
|
|
+ _tcscat_s(path, MAX_PATH, _T("touch_trace.log"));
|
|
|
+
|
|
|
+ TCHAR msg[1024] = { 0 };
|
|
|
+ va_list ap;
|
|
|
+ va_start(ap, fmt);
|
|
|
+ _vsntprintf_s(msg, _countof(msg), _TRUNCATE, fmt, ap);
|
|
|
+ va_end(ap);
|
|
|
+
|
|
|
+ // 统一转 UTF-8 写盘(Unicode/ANSI 工程都能用文本编辑器正常查看)
|
|
|
+ char utf8[2048] = { 0 };
|
|
|
+#ifdef _UNICODE
|
|
|
+ if (::WideCharToMultiByte(CP_UTF8, 0, msg, -1, utf8, (int)sizeof(utf8) - 1, NULL, NULL) <= 0)
|
|
|
+ return;
|
|
|
+#else
|
|
|
+ _tcscpy_s(utf8, _countof(utf8), msg);
|
|
|
+#endif
|
|
|
+ FILE* f = NULL;
|
|
|
+ if (fopen_s(&f, Narrow(path), "a") == 0 && f != NULL)
|
|
|
+ {
|
|
|
+ fprintf(f, "[%8u][T%5u] %s\n", (unsigned)::GetTickCount(), (unsigned)::GetCurrentThreadId(), utf8);
|
|
|
+ fclose(f);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
static void GetChildWndRect(HWND hWnd, HWND hChildWnd, RECT& rcChildWnd)
|
|
|
{
|
|
|
::GetWindowRect(hChildWnd, &rcChildWnd);
|
|
|
@@ -588,9 +658,11 @@ namespace DuiLib
|
|
|
// 走 MessageHandler 里原生的鼠标点击路径(CButtonUI 等据此 Activate())。NIM 从不过滤
|
|
|
// 这些模拟鼠标,只在 WM_LBUTTON* 分支加了 m_pEventTouch!=NULL 守卫,保证一次手指手势
|
|
|
// 要么滚动、要么点击,不会双触发。
|
|
|
- // 4) 守卫:DOWN 时若真实鼠标已在交互(m_pEventClick 被真实鼠标置位)则忽略触摸;MOVE 仅对“滚动条”让路
|
|
|
- // (原生鼠标独占滑块拖拽),容器照常收 TOUCHMOVE 跟手滚动。注意 m_pEventClick 也会被触摸模拟的
|
|
|
- // WM_LBUTTONDOWN 置位,故不能仅因它非空就吞掉 TOUCHMOVE,否则列表滚不动。
|
|
|
+ // 4) 守卫:DOWN 与 MOVE 都【只对“滚动条”让路】(滑块拖拽由原生鼠标独占),其余控件一律接管。
|
|
|
+ // 不能用 `m_pEventClick != NULL` 做通用守卫——Windows 为触摸补发的合成 WM_LBUTTONDOWN 也会
|
|
|
+ // 设置 m_pEventClick,一旦 WM_TOUCH 不是先到,通用守卫会让容器收不到 TOUCHMOVE(列表滚不动)。
|
|
|
+ // 5) 抬起后置 m_bSuppressHoverAfterTouch,抑制 Windows 补发的合成 MOUSEMOVE 与 WM_MOUSELEAVE
|
|
|
+ // 的自回发把刚清掉的悬浮态重新点亮(“悬浮态不消失 / 多控件同时悬浮”的真凶)。
|
|
|
TouchApi::PFN_GetTouchInputInfo pfnGetInfo = TouchApi::GetGetTouchInputInfo();
|
|
|
if (pfnGetInfo == NULL) return;
|
|
|
|
|
|
@@ -614,8 +686,12 @@ namespace DuiLib
|
|
|
|
|
|
if (ti.dwFlags & TouchApi::TOUCHEVTFLAG_DOWN)
|
|
|
{
|
|
|
- // 鼠标/指针交互进行中 -> 忽略触摸(NIM 同款守卫)
|
|
|
- if (m_pEventClick != NULL) break;
|
|
|
+ // 说明:这里【不再】用 `if (m_pEventClick != NULL) break;`。
|
|
|
+ // 该守卫本意是“真实鼠标交互中忽略触摸”,但 Windows 会在 WM_TOUCH 前后补发合成的
|
|
|
+ // WM_LBUTTONDOWN(同样会设置 m_pEventClick)。若保证不了 WM_TOUCH 一定先到,
|
|
|
+ // 这句 break 会让 m_pEventTouch 永不被设置 → 容器 TOUCHMOVE 跟手滚动彻底失效。
|
|
|
+ // 由于点击/滚动本质上互不冲突(点击走原生鼠标路径,滚动走 TOUCHMOVE),
|
|
|
+ // 这里只在“命中控件是滚动条”时让路,其余一律接管。
|
|
|
|
|
|
::SetFocus(m_hWndPaint);
|
|
|
m_ptLastTouchPos = pt;
|
|
|
@@ -628,13 +704,21 @@ namespace DuiLib
|
|
|
// 若此处再派发 UIEVENT_TOUCHMOVE 会冒泡到容器把【内容】滚走,与滑块拖拽打架→滑块拖不动、点轨道误翻页。
|
|
|
// 直接交回原生鼠标,本函数提前退出(悬浮态由 WM_TOUCH UP 统一清理)。
|
|
|
if (pControl->GetInterface(_T("ScrollBar")) != NULL)
|
|
|
+ {
|
|
|
+ TouchTrace::Log(_T("TOUCHDOWN-SCROLLBAR handback hit=%s pt=%d,%d click=%p capture=%d"),
|
|
|
+ pControl->GetName().GetData(), pt.x, pt.y, (void*)m_pEventClick, (int)m_bMouseCapture);
|
|
|
break;
|
|
|
+ }
|
|
|
|
|
|
m_pEventTouch = pControl;
|
|
|
m_bTouchMoved = false;
|
|
|
m_ptTouchStart = pt;
|
|
|
+ m_bSuppressHoverAfterTouch = false; // 新手势开始,解除上一轮抬起后的悬浮抑制
|
|
|
pControl->SetFocus();
|
|
|
SetCapture(); // mgr 级:m_bMouseCapture=true + ::SetCapture
|
|
|
+ TouchTrace::Log(_T("TOUCHDOWN hit=%s cls=%s pt=%d,%d click=%p capture=%d"),
|
|
|
+ pControl->GetName().GetData(), pControl->GetClass(), pt.x, pt.y,
|
|
|
+ (void*)m_pEventClick, (int)m_bMouseCapture);
|
|
|
|
|
|
TEventUI event = { 0 };
|
|
|
event.Type = UIEVENT_TOUCHDOWN;
|
|
|
@@ -651,7 +735,12 @@ namespace DuiLib
|
|
|
// 若整句 break,会把容器的 UIEVENT_TOUCHMOVE 一起吞掉→列表无法跟手滚动。
|
|
|
// 故只对“滚动条”让路(滚动条滑块拖拽由原生鼠标驱动,见 DOWN 的 ScrollBar break),
|
|
|
// 其余控件(容器/按钮)照常派发 TOUCHMOVE:容器据此 1:1 跟手滚动,按钮不处理 TOUCHMOVE 无害。
|
|
|
- if (m_pEventClick != NULL && m_pEventClick->GetInterface(_T("ScrollBar")) != NULL) break;
|
|
|
+ if (m_pEventClick != NULL && m_pEventClick->GetInterface(_T("ScrollBar")) != NULL)
|
|
|
+ {
|
|
|
+ TouchTrace::Log(_T("TOUCHMOVE-SKIP(click=scrollbar) click=%s pt=%d,%d"),
|
|
|
+ m_pEventClick->GetName().GetData(), pt.x, pt.y);
|
|
|
+ break;
|
|
|
+ }
|
|
|
// 与上一包完全相同的点 -> 去重,避免无谓派发(NIM 同款守卫)
|
|
|
if (m_ptLastTouchPos.x == pt.x && m_ptLastTouchPos.y == pt.y) break;
|
|
|
// 本次位移增量(相对上一包),随事件下发,滚动容器据此做 1:1 跟手平移
|
|
|
@@ -673,6 +762,10 @@ namespace DuiLib
|
|
|
event.wKeyState = MapKeyState();
|
|
|
event.wParam = (WPARAM)(int)dx;
|
|
|
event.lParam = (LPARAM)(int)dy;
|
|
|
+ TouchTrace::Log(_T("TOUCHMOVE touch=%s cls=%s d=%d,%d moved=%d click=%s"),
|
|
|
+ m_pEventTouch->GetName().GetData(), m_pEventTouch->GetClass(),
|
|
|
+ (int)dx, (int)dy, (int)m_bTouchMoved,
|
|
|
+ m_pEventClick != NULL ? m_pEventClick->GetName().GetData() : _T("null"));
|
|
|
m_pEventTouch->Event(event);
|
|
|
}
|
|
|
else if (ti.dwFlags & TouchApi::TOUCHEVTFLAG_UP)
|
|
|
@@ -713,6 +806,13 @@ namespace DuiLib
|
|
|
if (m_hwndTooltip != NULL) ::SendMessage(m_hwndTooltip, TTM_TRACKACTIVATE, FALSE, (LPARAM)&m_ToolTip);
|
|
|
}
|
|
|
|
|
|
+ // 关键:手指抬起后禁止“悬浮态复活”。
|
|
|
+ // Windows 会为触摸补发合成的 WM_MOUSEMOVE;且真实 WM_MOUSELEAVE 在 m_bMouseTracking
|
|
|
+ // 为真时会自己给自己重发 WM_MOUSEMOVE(见 WM_MOUSELEAVE 分支)→ 刚清掉的悬浮态立刻
|
|
|
+ // 被重新点亮。这里置抑制位 + 主动撤下鼠标跟踪,直到用户真的移动鼠标才恢复。
|
|
|
+ m_bSuppressHoverAfterTouch = true;
|
|
|
+ m_bMouseTracking = false;
|
|
|
+
|
|
|
// 滚动条等场景本函数未接管(m_pEventTouch 为 NULL),捕获由原生鼠标路径负责,这里不释放。
|
|
|
if (m_pEventTouch == NULL) break;
|
|
|
|
|
|
@@ -724,6 +824,9 @@ namespace DuiLib
|
|
|
event.ptMouse = pt;
|
|
|
event.dwTimestamp = ::GetTickCount();
|
|
|
event.wKeyState = MapKeyState();
|
|
|
+ TouchTrace::Log(_T("TOUCHUP touch=%s moved=%d hover=%p leaveArr=%d click=%p capture=%d"),
|
|
|
+ m_pEventTouch->GetName().GetData(), (int)m_bTouchMoved, (void*)m_pEventHover,
|
|
|
+ m_aNeedMouseLeaveNeeded.GetSize(), (void*)m_pEventClick, (int)m_bMouseCapture);
|
|
|
m_pEventTouch->Event(event);
|
|
|
m_pEventTouch = NULL;
|
|
|
}
|
|
|
@@ -1869,8 +1972,15 @@ namespace DuiLib
|
|
|
return true;
|
|
|
case WM_MOUSELEAVE:
|
|
|
{
|
|
|
+ TouchTrace::Log(_T("WM_MOUSELEAVE tracking=%d hover=%s leaveArr=%d click=%s"),
|
|
|
+ (int)m_bMouseTracking,
|
|
|
+ m_pEventHover != NULL ? m_pEventHover->GetName().GetData() : _T("null"),
|
|
|
+ m_aNeedMouseLeaveNeeded.GetSize(),
|
|
|
+ m_pEventClick != NULL ? m_pEventClick->GetName().GetData() : _T("null"));
|
|
|
if (m_hwndTooltip != NULL) ::SendMessage(m_hwndTooltip, TTM_TRACKACTIVATE, FALSE, (LPARAM)&m_ToolTip);
|
|
|
- if (m_bMouseTracking)
|
|
|
+ // 触屏抬起刚清过悬浮态时,不要执行下面这段“自回发 WM_MOUSEMOVE”:
|
|
|
+ // 光标通常仍在窗口内 → 会立刻重建悬浮态,导致“悬浮态不消失/多控件同时悬浮”。
|
|
|
+ if (!m_bSuppressHoverAfterTouch && m_bMouseTracking)
|
|
|
{
|
|
|
POINT pt = { 0 };
|
|
|
RECT rcWnd = { 0 };
|
|
|
@@ -1906,6 +2016,34 @@ namespace DuiLib
|
|
|
m_bMouseTracking = true;
|
|
|
}
|
|
|
|
|
|
+ // 触屏抬起后的“悬浮态复活”抑制:
|
|
|
+ // 抬起瞬间会置 m_bSuppressHoverAfterTouch=true。此后 Windows 补发的合成 MOUSEMOVE
|
|
|
+ // (以及 WM_MOUSELEAVE 触发的自回发 WM_MOUSEMOVE)一律不再参与悬浮态 enter/leave,
|
|
|
+ // 只更新坐标。真实鼠标移动(与上次触摸落点明显不同)才解除抑制。
|
|
|
+ {
|
|
|
+ POINT ptCheck = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
|
|
|
+ if ((INT_PTR)lParam == -1)
|
|
|
+ {
|
|
|
+ // (LPARAM)-1 = “鼠标离开窗口”的自回发,坐标为垃圾值,直接丢弃
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ if (m_bSuppressHoverAfterTouch)
|
|
|
+ {
|
|
|
+ const int kResumeSlop = 3; // 与上次触摸落点的最小差异,超过视为真实鼠标移动
|
|
|
+ int ddx = ptCheck.x - m_ptLastTouchPos.x;
|
|
|
+ int ddy = ptCheck.y - m_ptLastTouchPos.y;
|
|
|
+ if (ddx > kResumeSlop || ddx < -kResumeSlop || ddy > kResumeSlop || ddy < -kResumeSlop)
|
|
|
+ {
|
|
|
+ m_bSuppressHoverAfterTouch = false; // 真实鼠标移动,恢复悬浮逻辑
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ m_ptLastMousePos = ptCheck;
|
|
|
+ break; // 吞掉这次合成移动,不给控件改悬浮态
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
// Generate the appropriate mouse messages
|
|
|
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
|
|
|
// 是否移动
|
|
|
@@ -2005,6 +2143,9 @@ namespace DuiLib
|
|
|
static_cast<CControlUI*>(aNeedMouseLeaveNeeded[i])->Event(event);
|
|
|
}
|
|
|
|
|
|
+ TouchTrace::Log(_T("MOUSEMOVE-LEAVE old=%s leaveArr=%d new=%s pt=%d,%d"),
|
|
|
+ m_pEventHover->GetName().GetData(), m_aNeedMouseLeaveNeeded.GetSize(),
|
|
|
+ pNewHover != NULL ? pNewHover->GetName().GetData() : _T("null"), pt.x, pt.y);
|
|
|
m_pEventHover->Event(event);
|
|
|
m_pEventHover = NULL;
|
|
|
if (m_hwndTooltip != NULL) ::SendMessage(m_hwndTooltip, TTM_TRACKACTIVATE, FALSE, (LPARAM)&m_ToolTip);
|
|
|
@@ -2015,6 +2156,8 @@ namespace DuiLib
|
|
|
event.pSender = pNewHover;
|
|
|
pNewHover->Event(event);
|
|
|
m_pEventHover = pNewHover;
|
|
|
+ TouchTrace::Log(_T("HOVER-ENTER %s cls=%s pt=%d,%d"),
|
|
|
+ pNewHover->GetName().GetData(), pNewHover->GetClass(), pt.x, pt.y);
|
|
|
}
|
|
|
}
|
|
|
if (m_pEventClick != NULL)
|
|
|
@@ -2101,6 +2244,11 @@ namespace DuiLib
|
|
|
// 触屏拖拽手势(已发生位移)-> 松开不触发“点击”,避免“拖拽又误点”。
|
|
|
// 但滚动条滑块拖拽依赖 BUTTONUP 来解除 UISTATE_CAPTURED 并停掉 50ms 定时器,
|
|
|
// 绝不能在此提前清空 m_pEventClick,否则滚动条卡死在捕获态。故对滚动条放行 BUTTONUP。
|
|
|
+ TouchTrace::Log(_T("LBUTTONUP moved=%d click=%s hover=%s leaveArr=%d capture=%d"),
|
|
|
+ (int)m_bTouchMoved,
|
|
|
+ m_pEventClick != NULL ? m_pEventClick->GetName().GetData() : _T("null"),
|
|
|
+ m_pEventHover != NULL ? m_pEventHover->GetName().GetData() : _T("null"),
|
|
|
+ m_aNeedMouseLeaveNeeded.GetSize(), (int)m_bMouseCapture);
|
|
|
if (m_bTouchMoved && m_pEventClick != NULL && m_pEventClick->GetInterface(_T("ScrollBar")) == NULL)
|
|
|
m_pEventClick = NULL;
|
|
|
m_bTouchMoved = false; // 用完即清,绝不影响后续真实鼠标点击
|