| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224 |
- #include "StdAfx.h"
- #include "UIBase.h"
- #include "UIManager.h"
- #ifdef _DEBUG
- #include <shlwapi.h>
- #pragma comment(lib, "shlwapi.lib")
- #endif
- namespace DuiLib {
- // WM_TOUCH 消息 ID 在 winuser.h 里受 `#if _WIN32_WINNT >= 0x0601` 保护,
- // 而 DuiLib 工程的 _WIN32_WINNT 兜底为 XP(0x0501),直接引用会编译失败,这里自行声明。
- #ifndef WM_TOUCH
- #define WM_TOUCH 0x0240
- #endif
- namespace {
- // -------------------------------------------------------------------------
- // [触摸重复处理] 一次触摸手势会被处理两遍,第 2 遍必须作废。
- //
- // 第 1 遍:DuiLib 的触摸状态机(WM_TOUCH / WM_POINTER*)合成点击并派发业务回调;
- // 第 2 遍:Windows 把同一次触摸"提升"成的鼠标消息(extra 带 MOUSEEVENTF_FROMTOUCH,
- // 即 GetMessageExtraInfo() 高位 0xFF5157xx),排在消息队列里稍后投出。
- //
- // 第 2 遍是纯重复,造成的两类症状:
- // · Win7 点控件→回调里 ShowModal→"咚"(系统对"点击被禁用窗口"的默认处理 MessageBeep);
- // · 点弹框按钮后透传到背后主窗口控件。
- //
- // 处理分两处:
- // ① 投递之前让它不存在 —— CWindowWnd::ShowModal 进模态循环前用 PeekMessage 摘掉
- // 在途的 owner 鼠标副本(详见那里的注释)。这是"咚"的根治点。
- // ② 漏网的靠 CPaintManagerUI 里的 PROMOTED-DUP 闸门拦(见 UIManager.cpp)。
- //
- // 用户明确划定的边界(改动时务必守住):
- // · 点主窗口控件→回调弹框,那一声"咚"必须消除;
- // · 弹框**已经出现之后**点弹框外空白区的"咚"属正常反馈,**不要改**。
- // -------------------------------------------------------------------------
- typedef BOOL (WINAPI* PFN_REGISTER_TOUCH_WINDOW)(HWND, ULONG);
- void RegisterTouchWindowIfAvailable(HWND hWnd)
- {
- if (hWnd == NULL) return;
- HMODULE hUser32 = ::GetModuleHandle(_T("user32.dll"));
- if (hUser32 == NULL) return;
- PFN_REGISTER_TOUCH_WINDOW pfnRegister =
- reinterpret_cast<PFN_REGISTER_TOUCH_WINDOW>(::GetProcAddress(hUser32, "RegisterTouchWindow"));
- if (pfnRegister != NULL)
- pfnRegister(hWnd, 0);
- }
- // 【已搬移】FindManagerByPaintWindow 原本定义在这里(匿名 namespace 内)。
- //
- // 为什么必须搬走:UIBase.h 里为了让 UIManager.cpp 也能调用它,已经加了一份
- // 声明 `DuiLib::CPaintManagerUI* FindManagerByPaintWindow(HWND);`。
- // 于是同一个翻译单元里同时存在
- // · 匿名 namespace 内的 DuiLib::`anonymous-namespace'::FindManagerByPaintWindow
- // · 头文件声明的 DuiLib::FindManagerByPaintWindow
- // 两个候选,调用点(UIBase.cpp 里 793/1076/1124/1207/1367/1395 等)无法抉择,
- // MSVC 直接报 C2668「对重载函数的调用不明确」。
- // ⇒ 现在把它挪到匿名 namespace **之外**,作为 DuiLib 的正式成员,
- // 与头文件那份声明严格一一对应(定义见本文件下方 namespace 收尾之后)。
- // ---------------------------------------------------------------------------
- // [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);
- }
- // 把"原窗口过程"登记为转发目标(绑定到指定 HWND)。
- void BindRelayTarget(WNDPROC pfnOldProc, HWND hWnd)
- {
- 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 的状态机错乱。故整段删除,不再保留。
- }
- // ---------------------------------------------------------------------------
- // 真模态(DialogBox)辅助:按 paint window 找到对应的 CPaintManagerUI。
- //
- // ⚠️ 必须定义在**匿名 namespace 之外**(即 DuiLib 命名空间直接成员),
- // 因为 UIBase.h 里有一份同名声明供 UIManager.cpp 使用。若同时存在匿名
- // namespace 内的一份,调用点会因两个候选而报 C2668「调用不明确」。
- //
- // 用途:
- // · ShowModal 里要把 manager 从旧 HWND 迁到对话框新 HWND 时需要先找到它;
- // · CPaintManagerUI::IsModalBackdropWindow 判定"本句柄是否就是弹框自己的
- // manager 窗口"时也依赖它(透传闸门的一条关键判据)。
- // 实现:遍历 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;
- }
- /////////////////////////////////////////////////////////////////////////////////////
- //
- //
- void UILIB_API DUI__Trace(LPCTSTR pstrFormat, ...)
- {
- #ifdef _DEBUG
- TCHAR szBuffer[2048] = {0};
- va_list args;
- va_start(args, pstrFormat);
- _vsntprintf(szBuffer, 2048, pstrFormat, args);
- va_end(args);
-
- CDuiString strMsg = szBuffer;
- strMsg += _T("\n");
- OutputDebugString(strMsg.GetData());
- #endif
- }
- LPCTSTR DUI__TraceMsg(UINT uMsg)
- {
- #define MSGDEF(x) if(uMsg==x) return _T(#x)
- MSGDEF(WM_SETCURSOR);
- MSGDEF(WM_NCHITTEST);
- MSGDEF(WM_NCPAINT);
- MSGDEF(WM_PAINT);
- MSGDEF(WM_ERASEBKGND);
- MSGDEF(WM_NCMOUSEMOVE);
- MSGDEF(WM_MOUSEMOVE);
- MSGDEF(WM_MOUSELEAVE);
- MSGDEF(WM_MOUSEHOVER);
- MSGDEF(WM_NOTIFY);
- MSGDEF(WM_COMMAND);
- MSGDEF(WM_MEASUREITEM);
- MSGDEF(WM_DRAWITEM);
- MSGDEF(WM_LBUTTONDOWN);
- MSGDEF(WM_LBUTTONUP);
- MSGDEF(WM_LBUTTONDBLCLK);
- MSGDEF(WM_RBUTTONDOWN);
- MSGDEF(WM_RBUTTONUP);
- MSGDEF(WM_RBUTTONDBLCLK);
- MSGDEF(WM_SETFOCUS);
- MSGDEF(WM_KILLFOCUS);
- MSGDEF(WM_MOVE);
- MSGDEF(WM_SIZE);
- MSGDEF(WM_SIZING);
- MSGDEF(WM_MOVING);
- MSGDEF(WM_GETMINMAXINFO);
- MSGDEF(WM_CAPTURECHANGED);
- MSGDEF(WM_WINDOWPOSCHANGED);
- MSGDEF(WM_WINDOWPOSCHANGING);
- MSGDEF(WM_NCCALCSIZE);
- MSGDEF(WM_NCCREATE);
- MSGDEF(WM_NCDESTROY);
- MSGDEF(WM_TIMER);
- MSGDEF(WM_KEYDOWN);
- MSGDEF(WM_KEYUP);
- MSGDEF(WM_CHAR);
- MSGDEF(WM_SYSKEYDOWN);
- MSGDEF(WM_SYSKEYUP);
- MSGDEF(WM_SYSCOMMAND);
- MSGDEF(WM_SYSCHAR);
- MSGDEF(WM_VSCROLL);
- MSGDEF(WM_HSCROLL);
- MSGDEF(WM_CHAR);
- MSGDEF(WM_SHOWWINDOW);
- MSGDEF(WM_PARENTNOTIFY);
- MSGDEF(WM_CREATE);
- MSGDEF(WM_NCACTIVATE);
- MSGDEF(WM_ACTIVATE);
- MSGDEF(WM_ACTIVATEAPP);
- MSGDEF(WM_CLOSE);
- MSGDEF(WM_DESTROY);
- MSGDEF(WM_GETICON);
- MSGDEF(WM_GETTEXT);
- MSGDEF(WM_GETTEXTLENGTH);
- static TCHAR szMsg[10];
- ::wsprintf(szMsg, _T("0x%04X"), uMsg);
- return szMsg;
- }
- /////////////////////////////////////////////////////////////////////////////////////
- //
- //
- //////////////////////////////////////////////////////////////////////////
- //
- DUI_BASE_BEGIN_MESSAGE_MAP(CNotifyPump)
- DUI_END_MESSAGE_MAP()
- static const DUI_MSGMAP_ENTRY* DuiFindMessageEntry(const DUI_MSGMAP_ENTRY* lpEntry,TNotifyUI& msg )
- {
- CDuiString sMsgType = msg.sType;
- CDuiString sCtrlName = msg.pSender->GetName();
- const DUI_MSGMAP_ENTRY* pMsgTypeEntry = NULL;
- while (lpEntry->nSig != DuiSig_end)
- {
- if(lpEntry->sMsgType==sMsgType)
- {
- if(!lpEntry->sCtrlName.IsEmpty())
- {
- if(lpEntry->sCtrlName==sCtrlName)
- {
- return lpEntry;
- }
- }
- else
- {
- pMsgTypeEntry = lpEntry;
- }
- }
- lpEntry++;
- }
- return pMsgTypeEntry;
- }
- bool CNotifyPump::AddVirtualWnd(CDuiString strName,CNotifyPump* pObject)
- {
- if( m_VirtualWndMap.Find(strName) == NULL )
- {
- m_VirtualWndMap.Insert(strName.GetData(),(LPVOID)pObject);
- return true;
- }
- return false;
- }
- bool CNotifyPump::RemoveVirtualWnd(CDuiString strName)
- {
- if( m_VirtualWndMap.Find(strName) != NULL )
- {
- m_VirtualWndMap.Remove(strName);
- return true;
- }
- return false;
- }
- bool CNotifyPump::LoopDispatch(TNotifyUI& msg)
- {
- const DUI_MSGMAP_ENTRY* lpEntry = NULL;
- const DUI_MSGMAP* pMessageMap = NULL;
- #ifndef UILIB_STATIC
- for(pMessageMap = GetMessageMap(); pMessageMap!=NULL; pMessageMap = (*pMessageMap->pfnGetBaseMap)())
- #else
- for(pMessageMap = GetMessageMap(); pMessageMap!=NULL; pMessageMap = pMessageMap->pBaseMap)
- #endif
- {
- #ifndef UILIB_STATIC
- ASSERT(pMessageMap != (*pMessageMap->pfnGetBaseMap)());
- #else
- ASSERT(pMessageMap != pMessageMap->pBaseMap);
- #endif
- if ((lpEntry = DuiFindMessageEntry(pMessageMap->lpEntries,msg)) != NULL)
- {
- goto LDispatch;
- }
- }
- return false;
- LDispatch:
- union DuiMessageMapFunctions mmf;
- mmf.pfn = lpEntry->pfn;
- bool bRet = false;
- int nSig;
- nSig = lpEntry->nSig;
- switch (nSig)
- {
- default:
- ASSERT(FALSE);
- break;
- case DuiSig_lwl:
- (this->*mmf.pfn_Notify_lwl)(msg.wParam,msg.lParam);
- bRet = true;
- break;
- case DuiSig_vn:
- (this->*mmf.pfn_Notify_vn)(msg);
- bRet = true;
- break;
- }
- return bRet;
- }
- void CNotifyPump::NotifyPump(TNotifyUI& msg)
- {
- ///遍历虚拟窗口
- if( !msg.sVirtualWnd.IsEmpty() ){
- for( int i = 0; i< m_VirtualWndMap.GetSize(); i++ ) {
- if( LPCTSTR key = m_VirtualWndMap.GetAt(i) ) {
- if( _tcsicmp(key, msg.sVirtualWnd.GetData()) == 0 ){
- CNotifyPump* pObject = static_cast<CNotifyPump*>(m_VirtualWndMap.Find(key, false));
- if( pObject && pObject->LoopDispatch(msg) )
- return;
- }
- }
- }
- }
- ///
- //遍历主窗口
- LoopDispatch( msg );
- }
- //////////////////////////////////////////////////////////////////////////
- ///
- 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()
- {
- }
- HWND CWindowWnd::GetHWND() const
- {
- return m_hWnd;
- }
- UINT CWindowWnd::GetClassStyle() const
- {
- return 0;
- }
- LPCTSTR CWindowWnd::GetSuperClassName() const
- {
- return NULL;
- }
- CWindowWnd::operator HWND() const
- {
- return m_hWnd;
- }
- void CWindowWnd::EnableUnicode()
- {
- m_bUnicode = true;
- }
- HWND CWindowWnd::CreateDuiWindow( HWND hwndParent, LPCTSTR pstrWindowName,DWORD dwStyle /*=0*/, DWORD dwExStyle /*=0*/ )
- {
- return Create(hwndParent,pstrWindowName,dwStyle,dwExStyle,0,0,0,0,NULL);
- }
- HWND CWindowWnd::Create(HWND hwndParent, LPCTSTR pstrName, DWORD dwStyle, DWORD dwExStyle, const RECT rc, HMENU hMenu)
- {
- return Create(hwndParent, pstrName, dwStyle, dwExStyle, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, hMenu);
- }
- HWND CWindowWnd::Create(HWND hwndParent, LPCTSTR pstrName, DWORD dwStyle, DWORD dwExStyle, int x, int y, int cx, int cy, HMENU hMenu)
- {
- if( GetSuperClassName() != NULL && !RegisterSuperclass() ) return NULL;
- if( GetSuperClassName() == NULL && !RegisterWindowClass() ) return NULL;
- if(m_bUnicode) {
- #ifndef UNICODE
- LPWSTR lpClassName = a2w((char*)GetWindowClassName());
- LPWSTR lpName = a2w((char*)pstrName);
- #else
- LPWSTR lpClassName = (LPWSTR)GetWindowClassName();
- LPWSTR lpName = (LPWSTR)pstrName;
- #endif
- m_hWnd = ::CreateWindowExW(dwExStyle, lpClassName, lpName, dwStyle, x, y, cx, cy, hwndParent, hMenu, CPaintManagerUI::GetInstance(), this);
- #ifndef UNICODE
- delete []lpClassName;
- delete []lpName;
- #endif
- }
- else {
- m_hWnd = ::CreateWindowEx(dwExStyle, GetWindowClassName(), pstrName, dwStyle, x, y, cx, cy, hwndParent, hMenu, CPaintManagerUI::GetInstance(), this);
- }
- ASSERT(m_hWnd!=NULL);
- RegisterTouchWindowIfAvailable(m_hWnd);
- return m_hWnd;
- }
- HWND CWindowWnd::Subclass(HWND hWnd)
- {
- ASSERT(::IsWindow(hWnd));
- ASSERT(m_hWnd==NULL);
- m_OldWndProc = SubclassWindow(hWnd, __WndProc);
- if( m_OldWndProc == NULL ) return NULL;
- m_bSubclassed = true;
- m_hWnd = hWnd;
- ::SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LPARAM>(this));
- RegisterTouchWindowIfAvailable(m_hWnd);
- return m_hWnd;
- }
- void CWindowWnd::Unsubclass()
- {
- ASSERT(::IsWindow(m_hWnd));
- if( !::IsWindow(m_hWnd) ) return;
- if( !m_bSubclassed ) return;
- SubclassWindow(m_hWnd, m_OldWndProc);
- m_OldWndProc = ::DefWindowProc;
- m_bSubclassed = false;
- }
- void CWindowWnd::ShowWindow(bool bShow /*= true*/, bool bTakeFocus /*= false*/)
- {
- ASSERT(::IsWindow(m_hWnd));
- if( !::IsWindow(m_hWnd) ) return;
- ::ShowWindow(m_hWnd, bShow ? (bTakeFocus ? SW_SHOWNORMAL : SW_SHOWNOACTIVATE) : SW_HIDE);
- }
- 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));
- if( !::IsWindow(m_hWnd) ) return 0;
- HWND hWndOwner = ::GetWindow(m_hWnd, GW_OWNER);
- if( hWndOwner == NULL ) hWndOwner = ::GetActiveWindow();
- // ---- 1) 采集旧窗口的几何与样式,用于构造对话框模板 ----
- RECT rcWnd = { 0 };
- ::GetWindowRect(m_hWnd, &rcWnd);
- const int nW = rcWnd.right - rcWnd.left;
- const int nH = rcWnd.bottom - rcWnd.top;
- // 【尺寸精确性】旧窗口的客户区尺寸 —— 这是迁移后必须保持不变的量。
- //
- // 为什么不能直接用窗口矩形(nW/nH)去填 DLGTEMPLATE.cx/cy:
- // 1. DLGTEMPLATE 的 cx/cy 描述的是**客户区**尺寸,不是窗口外框尺寸;
- // 2. 我们给模板设的样式(WS_CAPTION/WS_DLGFRAME/DS_MODALFRAME…)会让系统
- // 在此基础上再往外加一圈非客户区边框。
- // 若直接填 nW/nH,新窗口 = 旧窗口 + 边框 ⇒ 弹框整体变大、位置也偏。
- //
- // 因此这里取"旧窗口客户区尺寸"作为目标客户区尺寸;创建完成后再在 __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 )
- {
- 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;
- // 记录"当前模态窗口",供库内其它地方判断(如 OnTouchInput 的老兜底逻辑)。
- // 注意:此时还不知道新 HWND,__DialogProc 里会再次 SetModalWindow。
- HWND hPrevModal = CPaintManagerUI::GetModalWindow();
- // 关键:先把旧窗口销毁、把 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();
- }
- // ---------------------------------------------------------------------
- // ⚠️⚠️⚠️【第 45 轮 · 悬垂 this 根治】销毁"壳窗口"之前,必须先把它的
- // GWLP_USERDATA 摘掉,否则本对象会被自己删掉。
- //
- // 机制(真机断言实证,崩溃点在 UIBase.cpp:674):
- // 这个壳窗口是本对象 `Create` 出来的 —— CWindowWnd::Create 走
- // `CreateWindowEx(..., this)`,__WndProc 的 WM_NCCREATE 会把 `this`
- // 写进该 HWND 的 GWLP_USERDATA。于是 ::DestroyWindow 时
- // __WndProc 必走 WM_NCDESTROY 分支 ⇒ `pThis->OnFinalMessage(hWnd)`。
- // 而**业务侧 40+ 个弹框类都把 OnFinalMessage 实现成 `delete this`**
- // (例:zhipuzi_pos_windows/wnd/CChufangSettingWnd.h:32):
- // void OnFinalMessage(HWND) { /*WindowImplBase::...*/ m_pm.RemovePreMessageFilter(this); delete this; }
- // ⇒ 对象在本函数**还没跑完**时就被自我销毁,随后
- // `DialogBoxIndirectParam(..., (LPARAM)this)` ← 第 674 行那个断言的现场
- // 交出去的是**已释放对象的指针**;__DialogProc 读 `pThis->m_hWnd` 拿到的是
- // Debug 堆 free 填充 0xDDDDDDDD(≠ NULL)⇒ `ASSERT(pThis->m_hWnd == NULL)` 失败。
- // (调用栈:CSettingPageUI::HandleClickMsg:1512 → ShowModal:630 → __DialogProc)
- //
- // 为什么"只摘 USERDATA"就够(不新增任何机制):
- // `delete this` 的职责**已经转移到对话框窗口**上 ——
- // __DialogProc 会把 `this` 写进**对话框**的 GWLP_USERDATA,
- // 对话框销毁时由 __DialogWndProc 的 WM_NCDESTROY 调 OnFinalMessage ⇒ 仍然只删一次。
- // 壳窗口被摘掉 USERDATA 后销毁时,__WndProc 里 `pThis == NULL` ⇒ 直接走
- // DefWindowProc,不会触发第二次 OnFinalMessage(无双重释放)。
- // 壳窗口此时已经没有存在价值:m_hWnd 已置空、manager 已 DetachPaintWindow、
- // 控件树已由 AttachPaintWindow 跟着 manager 搬到对话框。
- // ---------------------------------------------------------------------
- if( ::IsWindow(hWndOldCaptured) )
- {
- ::SetWindowLongPtr(hWndOldCaptured, GWLP_USERDATA, 0L);
- ::DestroyWindow(hWndOldCaptured);
- }
- // ---- 【Win7"咚"声 · 根治】把"在途的提升鼠标副本"从消息队列里摘掉 ----
- //
- // 时序(真机日志实证):
- // 调用方的触摸 UP 已经消费完(**当前正在执行的这段代码就是它的点击回调**),
- // 但同一次手势被系统提升出来的鼠标副本**还排在消息队列里没被投出来**;
- // 紧接着下面 DialogBoxIndirectParam 会把 owner `EnableWindow(FALSE)` 禁用,
- // 之后那条副本才被投递 ⇒ 撞上"点击禁用窗口"的默认处理 ⇒ **MessageBeep(0) 咚**。
- //
- // 为什么必须在这里摘、而不是在窗口过程里拦:
- // · 消息根本进不了窗口过程(系统在输入阶段就按"点击了禁用窗口"处理掉,消息根本不会到达窗口过程)
- // ⇒ 写在 MessageHandler 里的任何闸门,判据再准也无从生效;
- // · 此处摘除与"创建弹框"之间**没有任何消息泵** ⇒ 摘除对投递而言是**原子**的,
- // 不存在"刚摘完又被投一条"的窗口期。
- //
- // 摘除的是"**owner 窗口**的鼠标类消息",且只在"触摸通道确实收到了本次手势"时摘
- // (判据见 IsPromotedMouseDuplicatePending)—— 它们本来就是重复的:这次点击已经
- // 由触摸通道的 tap 判定处理完毕(否则走不到 ShowModal 这里)。因此两条边界都守住:
- // · 点主窗口控件点出弹框 ⇒ 摘掉 ⇒ **无声**(用户要求消除的那一声);
- // · 弹框已打开后点弹框外空白区 ⇒ 那处的 WM_TOUCH 在**输入阶段**就被系统丢弃、
- // 触摸通道根本没收到手势 ⇒ 标志为假 ⇒ 不摘 ⇒ **那一声"咚"照旧保留**
- // (用户明确要求不改的那一条边界)。
- if( hWndOwner != NULL && CPaintManagerUI::IsPromotedMouseDuplicatePending() )
- {
- CPaintManagerUI::DropQueuedPromotedMouseMessages(hWndOwner);
- }
- // ---- 4) 交给系统对话框管理器跑模态循环 ----
- // 这是阻塞调用,返回时模态已结束。
- INT_PTR nRet = ::DialogBoxIndirectParam(CPaintManagerUI::GetInstance(),
- pTemplate,
- hWndOwner,
- CWindowWnd::__DialogProc,
- (LPARAM)this);
- // ---------------------------------------------------------------------
- // ⚠️⚠️【第 45 轮】从这里往下**只能碰局部变量与静态函数**,绝不能再读写本对象的成员。
- //
- // 为什么:DialogBoxIndirectParam 返回时,对话框窗口已经销毁 ——
- // __DialogWndProc 的 WM_NCDESTROY 已经调过 `pThis->OnFinalMessage(hWnd)`,
- // 而业务侧弹框类的 OnFinalMessage 是 `delete this`
- // ⇒ 本对象此刻**已经被释放**。
- // 原来这里的 `m_bDoModal = false;` 与 `return m_nModalRet;` 就是在已释放内存上
- // 读写(一直没炸,只因那块堆内存尚未被复用),属同源隐患,一并收掉。
- //
- // 返回值从哪来:__DialogWndProc → Close() 里是 `::EndDialog(m_hWnd, (INT_PTR)nRet)`,
- // 而 DialogBoxIndirectParam 的返回值**就是**那个 nResult,与 m_nModalRet 恒等
- // ⇒ 直接取局部变量 nRet 即可,无需回头读成员。
- //
- // m_bDoModal 为什么正常路径不用复位:需要它的那个对象此刻已被销毁;若某个弹框类
- // 不删自己,它下次 ShowModal 的开头会重新 `m_bDoModal = true`,语义不变。
- // ---------------------------------------------------------------------
- const UINT nModalRetVal = ( nRet == -1 ) ? 0u : (UINT)nRet;
- // 资源可以释放了(DialogBoxIndirectParam 内部已把模板复制走)——
- // 但谨慎起见放在 EndDialog 之后,与 nim_duilib 一致。
- ::GlobalUnlock(hResource);
- ::GlobalFree(hResource);
- // 兜底:若 __DialogProc 因故没跑(创建失败 / WM_INITDIALOG 里 early-return),
- // 这里把中转变量清干净,避免悬垂指针影响后续 ShowModal。
- SetDetachedManager(NULL);
- CPaintManagerUI::SetModalWindow(hPrevModal);
- // DialogBoxIndirectParam 返回值:-1 表示创建失败
- if( nRet == -1 )
- {
- // 创建失败 ⇒ 对话框窗口从未存在 ⇒ __DialogProc 没跑 ⇒ 本对象还活着,
- // 这时才允许安全复位成员(否则下一次 ShowModal 会误判状态)。
- m_bDoModal = false;
- m_hWnd = NULL;
- return 0;
- }
- return nModalRetVal;
- }
- // -----------------------------------------------------------------------------
- // 真模态:对话框过程。
- //
- // 生命周期:
- // 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);
- // ---- 把旧的 CPaintManagerUI 挂到对话框窗口上 ----
- // 调用方在 ShowModal() 之前已 Create + manager.Init 到旧 HWND;这里把 manager 迁移过来,
- // 让 DuiLib 的全部既有逻辑(绘制 / 消息 / 控件树)在新窗口上继续工作。
- CPaintManagerUI* pManager = GetDetachedManager();
- if( pManager != NULL )
- {
- pManager->AttachPaintWindow(hDlg);
- // ---- [窗口阴影] 在对话框的新 HWND 上重建阴影窗口 ----
- // 背景:CShadowUI 的阴影窗口(PerryShadowWnd)是**绑定父窗口 HWND** 的 ——
- // · Create() 把 (父HWND → CShadowUI*) 记进 GetShadowMap(),以该 HWND 为
- // owner 创建阴影窗口,并把父窗口的窗口过程换成 ParentProc
- // (UIShadow.cpp:73-105);
- // · 父窗口被销毁时,ParentProc 收到 WM_DESTROY → DestroyWindow(阴影窗口),
- // 收到 WM_NCDESTROY → GetShadowMap().erase(父HWND)(UIShadow.cpp:238-243)。
- // 而本函数上方的真模态流程恰好把弹框**原来的 HWND 销毁了**:
- // DetachPaintWindow() → ::DestroyWindow(hWndOldCaptured) →
- // DialogBoxIndirectParam()。于是弹框的阴影窗口连同映射记录一起消失;而全工程
- // 唯一的 m_shadow.Create() 调用点在 CPaintManagerUI::AttachDialog
- // (UIManager.cpp:4918),**真模态路径不走它**(这里只调 AttachPaintWindow,
- // 它只换 m_hWndPaint / m_hDcPaint)⇒ 30+ 个模态弹框从此全都没有阴影窗口。
- // 主窗口不经过 ShowModal,所以它的阴影还在 —— 这正是
- // 「除了主窗口,所有模态弹框的窗口阴影都没了」的根因。
- //
- // 为什么直接调 Create() 就够:阴影样式(shadowsize / shadowcolor /
- // shadowsharpness / shadowposition …)早在 XML 解析时就写进了 manager 的
- // m_shadow 对象,重建只需重新绑 HWND,无需再解析一次 XML。
- // Create() 内部自带 m_bIsShowShadow 判断,没开阴影的窗口自然跳过。
- pManager->GetShadow()->Create(pManager);
- // 用掉即清:避免这个文件级静态变量残留一个已失效的指针,
- // 影响下一次 ShowModal(尤其嵌套弹框:A 弹框里再开 B 弹框)。
- SetDetachedManager(NULL);
- }
- // ---- [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);
- }
- // ---- 尺寸校正(关键)----
- // DLGTEMPLATE 里给的 cx/cy 是对话框单位(DLU)、且语义为「客户区」,系统创建时还会
- // 按实际样式额外加一圈非客户区边框。所以不能指望"模板尺寸 == 目标尺寸"。
- // 这里用 AdjustWindowRectEx 按对话框**真实**的样式反算目标外框,再 SetWindowPos 精确校正,
- // 保证客户区尺寸与原窗口逐像素一致 —— 这正是"弹框变大了"的修法。
- //
- // 放在 AttachPaintWindow 之后、HandleMessage(WM_INITDIALOG) 之前:
- // 此时 manager 已绑定新 HWND,尺寸变化会走正常的布局流程;且业务层 OnCreate 之前
- // 窗口就已经是最终尺寸,避免"先小后大"的闪动与一次性布局错位。
- {
- 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);
- // 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);
- }
- // ---- [窗口阴影] 尺寸校正后再刷新一次 ----
- // 上面的阴影是在尺寸校正**之前**建的,那时 hDlg 还是 DLGTEMPLATE 的初始尺寸,
- // 阴影位图因此是旧几何;SetWindowPos 之后窗口尺寸才最终确定,这里补刷一次,
- // 保证阴影与弹框逐像素对齐,而不依赖后续 WM_PAINT 的补正时机。
- if( pManager != NULL )
- {
- pManager->UpdateShadow();
- }
- // 交给 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;
- }
- // WM_INITDIALOG 已经由 __DialogProc 处理并 return TRUE,
- // 一般不会再走到这里;若系统仍投递(例如 EndDialog 之后的收尾路径),
- // 直接交回对话框默认过程,避免被 HandleMessage 当成普通消息误处理。
- if( uMsg == WM_INITDIALOG )
- return ::DefDlgProc(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);
- }
- return ::DefDlgProc(hWnd, uMsg, wParam, lParam);
- }
- void CWindowWnd::ShowModalFake()
- {
- ASSERT(::IsWindow(m_hWnd));
- auto p_hwnd = GetWindowOwner(m_hWnd);
- ASSERT(::IsWindow(p_hwnd));
- ASSERT(p_hwnd);
- ::EnableWindow(p_hwnd, FALSE);
- ShowWindow();
- m_bFakeModal = true;
- }
- void CWindowWnd::Close(UINT nRet)
- {
- if (m_bFakeModal)
- {
- auto parent_hwnd = GetWindowOwner(m_hWnd);
- ASSERT(::IsWindow(parent_hwnd));
- ::EnableWindow(parent_hwnd, TRUE);
- ::SetFocus(parent_hwnd);
- m_bFakeModal = false;
- }
- 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;
- // ---------------------------------------------------------------------
- ::EndDialog(m_hWnd, (INT_PTR)nRet);
- return;
- }
- PostMessage(WM_CLOSE, (WPARAM)nRet, 0L);
- }
- void CWindowWnd::CenterWindow()
- {
- ASSERT(::IsWindow(m_hWnd));
- ASSERT((GetWindowStyle(m_hWnd)&WS_CHILD)==0);
- RECT rcDlg = { 0 };
- ::GetWindowRect(m_hWnd, &rcDlg);
- RECT rcArea = { 0 };
- RECT rcCenter = { 0 };
- HWND hWnd=*this;
- HWND hWndParent = ::GetParent(m_hWnd);
- HWND hWndCenter = ::GetWindowOwner(m_hWnd);
- if (hWndCenter!=NULL)
- hWnd=hWndCenter;
- // 处理多显示器模式下屏幕居中
- MONITORINFO oMonitor = {};
- oMonitor.cbSize = sizeof(oMonitor);
- ::GetMonitorInfo(::MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST), &oMonitor);
- rcArea = oMonitor.rcWork;
- if( hWndCenter == NULL )
- rcCenter = rcArea;
- else
- ::GetWindowRect(hWndCenter, &rcCenter);
- int DlgWidth = rcDlg.right - rcDlg.left;
- int DlgHeight = rcDlg.bottom - rcDlg.top;
- // Find dialog's upper left based on rcCenter
- int xLeft = (rcCenter.left + rcCenter.right) / 2 - DlgWidth / 2;
- int yTop = (rcCenter.top + rcCenter.bottom) / 2 - DlgHeight / 2;
- // The dialog is outside the screen, move it inside
- if( xLeft < rcArea.left ) xLeft = rcArea.left;
- else if( xLeft + DlgWidth > rcArea.right ) xLeft = rcArea.right - DlgWidth;
- if( yTop < rcArea.top ) yTop = rcArea.top;
- else if( yTop + DlgHeight > rcArea.bottom ) yTop = rcArea.bottom - DlgHeight;
- ::SetWindowPos(m_hWnd, NULL, xLeft, yTop, -1, -1, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
- }
- void CWindowWnd::SetIcon(UINT nRes)
- {
- HICON hIcon = (HICON)::LoadImage(CPaintManagerUI::GetInstance(), MAKEINTRESOURCE(nRes), IMAGE_ICON,
- (::GetSystemMetrics(SM_CXICON) + 15) & ~15, (::GetSystemMetrics(SM_CYICON) + 15) & ~15, // 防止高DPI下图标模糊
- LR_DEFAULTCOLOR);
- ASSERT(hIcon);
- ::SendMessage(m_hWnd, WM_SETICON, (WPARAM) TRUE, (LPARAM) hIcon);
- hIcon = (HICON)::LoadImage(CPaintManagerUI::GetInstance(), MAKEINTRESOURCE(nRes), IMAGE_ICON,
- (::GetSystemMetrics(SM_CXICON) + 15) & ~15, (::GetSystemMetrics(SM_CYICON) + 15) & ~15, // 防止高DPI下图标模糊
- LR_DEFAULTCOLOR);
- ASSERT(hIcon);
- ::SendMessage(m_hWnd, WM_SETICON, (WPARAM) FALSE, (LPARAM) hIcon);
- }
- bool CWindowWnd::RegisterWindowClass()
- {
- WNDCLASS wc = { 0 };
- wc.style = GetClassStyle();
- wc.cbClsExtra = 0;
- wc.cbWndExtra = 0;
- wc.hIcon = NULL;
- wc.lpfnWndProc = CWindowWnd::__WndProc;
- wc.hInstance = CPaintManagerUI::GetInstance();
- wc.hCursor = ::LoadCursor(NULL, IDC_ARROW);
- wc.hbrBackground = NULL;
- wc.lpszMenuName = NULL;
- wc.lpszClassName = GetWindowClassName();
- ATOM ret = ::RegisterClass(&wc);
- ASSERT(ret!=NULL || ::GetLastError()==ERROR_CLASS_ALREADY_EXISTS);
- return ret != NULL || ::GetLastError() == ERROR_CLASS_ALREADY_EXISTS;
- }
- bool CWindowWnd::RegisterSuperclass()
- {
- if(m_bUnicode) {
- #ifndef UNICODE
- LPWSTR lpSuperClassName = a2w((char*)GetSuperClassName());
- LPWSTR lpClassName = a2w((char*)GetWindowClassName());
- #else
- LPWSTR lpSuperClassName = (LPWSTR)GetSuperClassName();
- LPWSTR lpClassName = (LPWSTR)GetWindowClassName();
- #endif
- // Get the class information from an existing
- // window so we can subclass it later on...
- WNDCLASSEXW wc = { 0 };
- wc.cbSize = sizeof(WNDCLASSEXW);
- if( !::GetClassInfoExW(NULL, lpSuperClassName, &wc) ) {
- if( !::GetClassInfoExW(CPaintManagerUI::GetInstance(), lpSuperClassName, &wc) ) {
- ASSERT(!"Unable to locate window class");
- return NULL;
- }
- }
- m_OldWndProc = wc.lpfnWndProc;
- wc.lpfnWndProc = CWindowWnd::__ControlProc;
- wc.hInstance = CPaintManagerUI::GetInstance();
- wc.lpszClassName = lpClassName;
- ATOM ret = ::RegisterClassExW(&wc);
- #ifndef UNICODE
- delete []lpClassName;
- delete []lpSuperClassName;
- #endif
- ASSERT(ret!=NULL || ::GetLastError()==ERROR_CLASS_ALREADY_EXISTS);
- return ret != NULL || ::GetLastError() == ERROR_CLASS_ALREADY_EXISTS;
- }
- else {
- // Get the class information from an existing
- // window so we can subclass it later on...
- WNDCLASSEX wc = { 0 };
- wc.cbSize = sizeof(WNDCLASSEX);
- if( !::GetClassInfoEx(NULL, GetSuperClassName(), &wc) ) {
- if( !::GetClassInfoEx(CPaintManagerUI::GetInstance(), GetSuperClassName(), &wc) ) {
- ASSERT(!"Unable to locate window class");
- return NULL;
- }
- }
- m_OldWndProc = wc.lpfnWndProc;
- wc.lpfnWndProc = CWindowWnd::__ControlProc;
- wc.hInstance = CPaintManagerUI::GetInstance();
- wc.lpszClassName = GetWindowClassName();
- ATOM ret = ::RegisterClassEx(&wc);
- ASSERT(ret!=NULL || ::GetLastError()==ERROR_CLASS_ALREADY_EXISTS);
- return ret != NULL || ::GetLastError() == ERROR_CLASS_ALREADY_EXISTS;
- }
- }
- // 注:RelayWndProc / BindRelayTarget 已上移到文件前部的**匿名 namespace 内**
- // (约 308 / 317 行),因为 __DialogWndProc 比本函数先出现、也要用到它们。
- // 另注:FindManagerByPaintWindow 也已移到匿名 namespace **之外**(约 350 行),
- // 以便与 UIBase.h 里的同名声明一一对应(否则调用点会报 C2668 二义)。
- LRESULT CALLBACK CWindowWnd::__WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
- { CWindowWnd* pThis = NULL;
- if( uMsg == WM_NCCREATE ) {
- LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam);
- pThis = static_cast<CWindowWnd*>(lpcs->lpCreateParams);
- pThis->m_hWnd = hWnd;
- ::SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LPARAM>(pThis));
- }
- else {
- 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);
- // ⚠️【第 45 轮】这里必须用实参 hWnd,不能写 pThis->m_hWnd:
- // 真模态路径下 ShowModal 会**先把 m_hWnd 置空**再去销毁窗口(见那里的长注释),
- // 此时 pThis->m_hWnd == NULL,用它去 SetWindowLongPtr 等于什么都没做
- // ⇒ 该 HWND 的 GWLP_USERDATA 会留着已死的 this,直到句柄被系统复用。
- ::SetWindowLongPtr(hWnd, GWLP_USERDATA, 0L);
- if( pThis->m_bSubclassed ) pThis->Unsubclass();
- pThis->m_hWnd = NULL;
- pThis->OnFinalMessage(hWnd);
- return lRes;
- }
- }
- 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 {
- return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
- }
- }
- LRESULT CALLBACK CWindowWnd::__ControlProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
- {
- CWindowWnd* pThis = NULL;
- if( uMsg == WM_NCCREATE ) {
- LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam);
- pThis = static_cast<CWindowWnd*>(lpcs->lpCreateParams);
- ::SetProp(hWnd, _T("WndX"), (HANDLE) pThis);
- pThis->m_hWnd = hWnd;
- }
- else {
- pThis = reinterpret_cast<CWindowWnd*>(::GetProp(hWnd, _T("WndX")));
- if( uMsg == WM_NCDESTROY && pThis != NULL ) {
- LRESULT lRes = ::CallWindowProc(pThis->m_OldWndProc, hWnd, uMsg, wParam, lParam);
- if( pThis->m_bSubclassed ) pThis->Unsubclass();
- ::SetProp(hWnd, _T("WndX"), NULL);
- pThis->m_hWnd = NULL;
- pThis->OnFinalMessage(hWnd);
- return lRes;
- }
- }
- if( pThis != NULL ) {
- return pThis->HandleMessage(uMsg, wParam, lParam);
- }
- else {
- return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
- }
- }
- LRESULT CWindowWnd::SendMessage(UINT uMsg, WPARAM wParam /*= 0*/, LPARAM lParam /*= 0*/)
- {
- ASSERT(::IsWindow(m_hWnd));
- return ::SendMessage(m_hWnd, uMsg, wParam, lParam);
- }
- LRESULT CWindowWnd::PostMessage(UINT uMsg, WPARAM wParam /*= 0*/, LPARAM lParam /*= 0*/)
- {
- ASSERT(::IsWindow(m_hWnd));
- return ::PostMessage(m_hWnd, uMsg, wParam, lParam);
- }
- void CWindowWnd::ResizeClient(int cx /*= -1*/, int cy /*= -1*/)
- {
- ASSERT(::IsWindow(m_hWnd));
- RECT rc = { 0 };
- if( !::GetClientRect(m_hWnd, &rc) ) return;
- if( cx != -1 ) rc.right = cx;
- if( cy != -1 ) rc.bottom = cy;
- if( !::AdjustWindowRectEx(&rc, GetWindowStyle(m_hWnd), (!(GetWindowStyle(m_hWnd) & WS_CHILD) && (::GetMenu(m_hWnd) != NULL)), GetWindowExStyle(m_hWnd)) ) return;
- ::SetWindowPos(m_hWnd, NULL, 0, 0, rc.right - rc.left, rc.bottom - rc.top, SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE);
- }
- LRESULT CWindowWnd::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
- {
- return ::CallWindowProc(m_OldWndProc, m_hWnd, uMsg, wParam, lParam);
- }
- void CWindowWnd::OnFinalMessage(HWND /*hWnd*/)
- {
- }
- } // namespace DuiLib
|