|
|
@@ -112,6 +112,75 @@ namespace {
|
|
|
void SetDetachedManager(DuiLib::CPaintManagerUI* pManager) { g_pDetachedManager = pManager; }
|
|
|
DuiLib::CPaintManagerUI* GetDetachedManager() { return g_pDetachedManager; }
|
|
|
|
|
|
+ // -------------------------------------------------------------------------
|
|
|
+ // 【R49d · 临时诊断】"ESC / 回车到底被谁收走了" —— 线程局部 WH_GETMESSAGE 嗅探。
|
|
|
+ //
|
|
|
+ // 真模态弹框的 ESC / 回车有三条消失路径,前两条在窗口过程里能看见,
|
|
|
+ // **第三条只有在"消息出队、但还没被派发"时才能看见**:
|
|
|
+ // ① DispatchMessage 直接派发给弹框
|
|
|
+ // (我们回答 WM_GETDLGCODE = DLGC_WANTALLKEYS 之后就应该走这条);
|
|
|
+ // ② 被 IsDialogMessage 改写成 WM_COMMAND(IDCANCEL / IDOK)
|
|
|
+ // (__DialogWndProc 里再翻译回合成的 WM_KEYDOWN);
|
|
|
+ // ③ 压根没投给弹框 —— 键盘焦点跑去了别的窗口(主窗口 / 屏幕键盘 / 别的进程)。
|
|
|
+ // WH_GETMESSAGE 钩子正好卡在 GetMessage 返回之前,是唯一能分辨 ③ 的观察点。
|
|
|
+ //
|
|
|
+ // 只在真模态弹框存活期间安装(引用计数,嵌套弹框只装一次),只记录
|
|
|
+ // ESC / 回车 / Tab 三个键 ⇒ 日志量极小。输出:exe 同目录 dlgkey_trace.log。
|
|
|
+ //
|
|
|
+ // ⚠️ 纯诊断,与业务逻辑无关。问题定位后整体删除:
|
|
|
+ // 连同 SpawnKeySpy / StopKeySpy 两个调用点、以及 UIDiag 里的 LogKey 一起删。
|
|
|
+ // -------------------------------------------------------------------------
|
|
|
+ int g_nKeySpyRef = 0;
|
|
|
+ HHOOK g_hKeySpy = NULL;
|
|
|
+
|
|
|
+ LRESULT CALLBACK KeySpyProc(int nCode, WPARAM wParam, LPARAM lParam)
|
|
|
+ {
|
|
|
+ if (nCode == HC_ACTION)
|
|
|
+ {
|
|
|
+ const MSG* pMsg = reinterpret_cast<const MSG*>(lParam);
|
|
|
+ if (pMsg != NULL && (pMsg->message == WM_KEYDOWN || pMsg->message == WM_SYSKEYDOWN))
|
|
|
+ {
|
|
|
+ const unsigned nKey = (unsigned)pMsg->wParam;
|
|
|
+ if (nKey == VK_ESCAPE || nKey == VK_RETURN || nKey == VK_TAB)
|
|
|
+ {
|
|
|
+ TCHAR szCls[64] = { 0 };
|
|
|
+ ::GetClassName(pMsg->hwnd, szCls, 63);
|
|
|
+ DuiLibDiag::LogKey(
|
|
|
+ _T("[SPY] key=0x%02X target=%p cls=%s focus=%p active=%p fg=%p"),
|
|
|
+ nKey, pMsg->hwnd, szCls,
|
|
|
+ ::GetFocus(), ::GetActiveWindow(), ::GetForegroundWindow());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return ::CallNextHookEx(g_hKeySpy, nCode, wParam, lParam);
|
|
|
+ }
|
|
|
+
|
|
|
+ void SpawnKeySpy()
|
|
|
+ {
|
|
|
+ ++g_nKeySpyRef;
|
|
|
+ if (g_hKeySpy == NULL)
|
|
|
+ {
|
|
|
+ g_hKeySpy = ::SetWindowsHookEx(WH_GETMESSAGE, KeySpyProc, NULL, ::GetCurrentThreadId());
|
|
|
+ DuiLibDiag::LogKey(_T("[SPY] install ref=%d hook=%p tid=%u"),
|
|
|
+ g_nKeySpyRef, g_hKeySpy, (unsigned)::GetCurrentThreadId());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ void StopKeySpy()
|
|
|
+ {
|
|
|
+ if (g_nKeySpyRef > 0) --g_nKeySpyRef;
|
|
|
+ if (g_nKeySpyRef <= 0)
|
|
|
+ {
|
|
|
+ g_nKeySpyRef = 0;
|
|
|
+ if (g_hKeySpy != NULL)
|
|
|
+ {
|
|
|
+ ::UnhookWindowsHookEx(g_hKeySpy);
|
|
|
+ DuiLibDiag::LogKey(_T("[SPY] uninstall hook=%p"), g_hKeySpy);
|
|
|
+ g_hKeySpy = NULL;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
// 【已移除】IsModalBlockedInputMessage —— 假模态时代的"在消息循环里丢弃发给 owner 的输入消息"。
|
|
|
// 现在 ShowModal 走真模态(DialogBoxIndirectParam),系统对话框管理器自带 owner 禁用,
|
|
|
// 这条兜底不但多余,还会误伤:真模态期间 owner 本就需要收到 WM_ACTIVATE/WM_KILLFOCUS 之类的
|
|
|
@@ -746,6 +815,12 @@ INT_PTR CALLBACK CWindowWnd::__DialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, L
|
|
|
{
|
|
|
pManager->AttachPaintWindow(hDlg);
|
|
|
|
|
|
+ // ★【R50】补一次"全新窗口"的首次布局 + 首次布局完成通知。
|
|
|
+ // 壳窗口阶段 AttachDialog 已经把 m_bFirstLayout / m_bUpdateNeeded 置过 true,
|
|
|
+ // 一旦在壳窗口那边被消费掉,弹框这个新 HWND 上就再也不会发 windowinit
|
|
|
+ // ⇒ 业务层挂 windowinit 的初始化(默认焦点等)不执行。
|
|
|
+ pManager->RewindForModalPaint();
|
|
|
+
|
|
|
// ---- [窗口阴影] 在对话框的新 HWND 上重建阴影窗口 ----
|
|
|
// 背景:CShadowUI 的阴影窗口(PerryShadowWnd)是**绑定父窗口 HWND** 的 ——
|
|
|
// · Create() 把 (父HWND → CShadowUI*) 记进 GetShadowMap(),以该 HWND 为
|
|
|
@@ -840,6 +915,10 @@ INT_PTR CALLBACK CWindowWnd::__DialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, L
|
|
|
pThis->HandleMessage(uMsg, wParam, lParam);
|
|
|
|
|
|
::SetFocus(hDlg);
|
|
|
+
|
|
|
+ // [R49d 诊断] 装键盘嗅探钩子(引用计数;见 KeySpyProc 的注释)。
|
|
|
+ SpawnKeySpy();
|
|
|
+
|
|
|
return TRUE; // TRUE = 我们自己设置了焦点
|
|
|
}
|
|
|
|
|
|
@@ -856,6 +935,8 @@ LRESULT CALLBACK CWindowWnd::__DialogWndProc(HWND hWnd, UINT uMsg, WPARAM wParam
|
|
|
|
|
|
if( uMsg == WM_NCDESTROY && pThis != NULL )
|
|
|
{
|
|
|
+ // [R49d 诊断] 弹框销毁即卸载键盘嗅探(引用计数归零才真正 Unhook)。
|
|
|
+ StopKeySpy();
|
|
|
// 顺序很关键:
|
|
|
// 1) 先把窗口过程还原(此后系统调用会回到对话框默认过程);
|
|
|
// 2) 再让 DefWindowProc 做标准清理;
|
|
|
@@ -895,6 +976,78 @@ LRESULT CALLBACK CWindowWnd::__DialogWndProc(HWND hWnd, UINT uMsg, WPARAM wParam
|
|
|
|
|
|
if( pThis != NULL )
|
|
|
{
|
|
|
+ // -------------------------------------------------------------------------
|
|
|
+ // ★★★【R49c】把「对话框级导航键」从对话框管理器手里要回来
|
|
|
+ // —— 根治「真模态弹框 ESC / 回车完全没反应」。
|
|
|
+ //
|
|
|
+ // 【现象】真模态弹框(#32770)里按 ESC / 回车毫无反应;
|
|
|
+ // 而全库 39 个弹框都把
|
|
|
+ // 「WM_KEYDOWN + VK_ESCAPE / VK_RETURN」分支写在
|
|
|
+ // IMessageFilterUI::MessageHandler(pre-filter)里,看上去一点问题都没有。
|
|
|
+ //
|
|
|
+ // 【根因】不是我们的代码,是 Windows 对话框管理器的既定行为:
|
|
|
+ // DialogBoxIndirectParam 内部跑的是
|
|
|
+ // while(GetMessage()) {
|
|
|
+ // if (!IsDialogMessage(hDlg, &msg)) { TranslateMessage; DispatchMessage; }
|
|
|
+ // }
|
|
|
+ // ⇒ 键盘消息先过 IsDialogMessage。它会(参考 Raymond Chen
|
|
|
+ // 《Exploring the signals the dialog manager uses for dismissing a dialog》):
|
|
|
+ // · VK_ESCAPE -> 改写成 WM_COMMAND(wParam=LOWORD(IDCANCEL)=2, HIWORD=BN_CLICKED)
|
|
|
+ // · VK_RETURN -> 改写成 WM_COMMAND(wParam=LOWORD(IDOK)=1, HIWORD=BN_CLICKED)
|
|
|
+ // 然后 **return TRUE**(消息已被吃,不再 DispatchMessage)
|
|
|
+ // ⇒ 窗口过程永远收不到这两个 WM_KEYDOWN。Petzold《Programming Windows》
|
|
|
+ // 原文也点明:“there is no requirement that controls with IDOK or
|
|
|
+ // IDCANCEL IDs exist” —— 即弹框一个子控件都没有(本项目
|
|
|
+ // DLGTEMPLATE 的 cdit=0)也照样发。
|
|
|
+ //
|
|
|
+ // 【为什么上一轮(#46)没顺带修好】那一轮补的 PreMessageHandler
|
|
|
+ // 只解决「PostMessage 过来的自定义消息(WM_SHANGPIN_HANDLE_SUCCESS 等)
|
|
|
+ // 到不了 pre-filter」—— 那些是**投递消息**,不经过
|
|
|
+ // IsDialogMessage;而 ESC / 回车是**键盘消息**,在更前面就被
|
|
|
+ // 改写掉了。⇒ 现象永远对不上:保存成功能收到消息,ESC 却毫无反应。
|
|
|
+ //
|
|
|
+ // 【修法】两条路一起上,互为兜底,且**绝不可能重复触发**:
|
|
|
+ // ① 回答 WM_GETDLGCODE = DLGC_WANTALLKEYS —— 这是对话框管理器
|
|
|
+ // 官方的“逃生口”:按 Raymond Chen 原文,只要焦点控件在
|
|
|
+ // WM_GETDLGCODE 里声明 DLGC_WANTALLKEYS / DLGC_WANTMESSAGE,
|
|
|
+ // IsDialogMessage 就**不做**上面那套改写,把 WM_KEYDOWN 原样
|
|
|
+ // DispatchMessage 下来。(顺带把 Tab / 方向键导航还给 DuiLib
|
|
|
+ // 自己的 PreMessageHandler。)
|
|
|
+ // ② 万一系统没理会 ①(或焦点落在不声明 DLGCODE 的**原生 EDIT
|
|
|
+ // 子窗口**上 —— 那时查询对象是那个子窗口而不是本弹框),
|
|
|
+ // ESC / 回车就会被改写成 WM_COMMAND。那就把
|
|
|
+ // WM_COMMAND(IDCANCEL / IDOK) 翻译回一条合成的 WM_KEYDOWN,
|
|
|
+ // 先喂给 pre-filter 链 —— 39 个弹框里既有的分支原样生效,
|
|
|
+ // 业务代码一行不用改。判据与对话框管理器一致:
|
|
|
+ // 只在「弹框里没有 ID == IDCANCEL / IDOK 的控件」时才翻译
|
|
|
+ // (本项目 DLGTEMPLATE 无任何控件,恒成立;有真实控件时放手
|
|
|
+ // 不管,绝不误伤)。
|
|
|
+ //
|
|
|
+ // 【为什么两条路不可能同时生效】同一次按键要么被
|
|
|
+ // IsDialogMessage 吃掉(走 ②),要么被 DispatchMessage 下来(走 ①)
|
|
|
+ // —— 互斥,所以不会一次按键关两次弹框。
|
|
|
+ // -------------------------------------------------------------------------
|
|
|
+ // [R49d 诊断] 弹框窗口过程实际收到了什么(量极小,只在 ESC/回车/Tab 与
|
|
|
+ // WM_COMMAND(IDCANCEL/IDOK) 上落笔)—— 用来分辨"被 IsDialogMessage 改写了"
|
|
|
+ // 还是"原样派发下来了"。
|
|
|
+ if( uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN )
|
|
|
+ {
|
|
|
+ const unsigned nKey = (unsigned)wParam;
|
|
|
+ if( nKey == VK_ESCAPE || nKey == VK_RETURN || nKey == VK_TAB )
|
|
|
+ DuiLibDiag::LogKey(_T("[DLGWP] KEYDOWN key=0x%02X hwnd=%p focus=%p"),
|
|
|
+ nKey, hWnd, ::GetFocus());
|
|
|
+ }
|
|
|
+ else if( uMsg == WM_COMMAND )
|
|
|
+ {
|
|
|
+ const unsigned nId = (unsigned)LOWORD(wParam);
|
|
|
+ if( nId == IDCANCEL || nId == IDOK )
|
|
|
+ DuiLibDiag::LogKey(_T("[DLGWP] COMMAND id=%u hi=0x%04X hwnd=%p"),
|
|
|
+ nId, (unsigned)HIWORD(wParam), hWnd);
|
|
|
+ }
|
|
|
+
|
|
|
+ if( uMsg == WM_GETDLGCODE )
|
|
|
+ return DLGC_WANTALLKEYS;
|
|
|
+
|
|
|
// ---------------------------------------------------------------
|
|
|
// [WM_TOUCH 归属权] 注册"补调原窗口过程"的钩子。
|
|
|
//
|
|
|
@@ -933,6 +1086,21 @@ LRESULT CALLBACK CWindowWnd::__DialogWndProc(HWND hWnd, UINT uMsg, WPARAM wParam
|
|
|
// 这里在分发 WNDPROC 之前补跑本窗口的 pre-filter 链(pMgr->PreMessageHandler),
|
|
|
// 完全等价于主循环行为(pre-filter 优先,未处理再走 HandleMessage),彻底根除该回归。
|
|
|
// 取不到 manager 时降级为原行为,不引入新回归;WM_TOUCH 等 pre-filter 不处理的消息照常回落。
|
|
|
+ // ★【R49c】见上方 WM_GETDLGCODE 处的长注释 ②:把对话框管理器
|
|
|
+ // 改写出来的 WM_COMMAND(IDCANCEL / IDOK) 翻译回一条合成的
|
|
|
+ // WM_KEYDOWN,先喂 pre-filter 链。两条路互斥,不会重复触发。
|
|
|
+ if( uMsg == WM_COMMAND && pMgr != NULL )
|
|
|
+ {
|
|
|
+ const WORD wId = LOWORD(wParam);
|
|
|
+ if( ( wId == IDCANCEL || wId == IDOK ) && ::GetDlgItem(hWnd, wId) == NULL )
|
|
|
+ {
|
|
|
+ const WPARAM wKey = ( wId == IDCANCEL ) ? (WPARAM)VK_ESCAPE : (WPARAM)VK_RETURN;
|
|
|
+ LRESULT lPreKey = 0;
|
|
|
+ if( pMgr->PreMessageHandler(WM_KEYDOWN, wKey, lParam, lPreKey) )
|
|
|
+ return lPreKey;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
if (pMgr != NULL)
|
|
|
{
|
|
|
LRESULT lPre = 0;
|