|
|
VC++让整个对话框“完全不可操作”而不去枚举每个控件,最稳健的做法是放一个覆盖层(overlay)在对话框的客户区上,拦截所有鼠标/键盘事件。
要让整个对话框“完全不可操作”而不去枚举每个控件,最稳健的做法是放一个覆盖层(overlay)在对话框的客户区上,拦截所有鼠标/键盘事件。这样用户看起来不能操作窗口的任何控件(不需要逐个 EnableWindow(FALSE)),并且在任务完成时只需销毁覆盖层即可恢复交互。
我给出两个方案并推荐第一种(覆盖层),因为它不修改现有控件,也容易在任务开始/结束时开启/关闭。
方案 A(推荐)—— 输入拦截覆盖层(CInputBlocker)
- 创建一个专门的 CWnd 子窗口,覆盖整个对话框客户区。
- 覆盖层接收焦点并捕获鼠标,从而拦截鼠标和键盘。可显示等待光标或半透明遮罩(简单背景色即可)。
- 窗口大小随对话框调整(在 WM_SIZE 中调整覆盖层大小)。
示例实现(精简,可直接拷贝到你的项目并按需调整)。
1) CInputBlocker.h
- #pragma once
- #include <afxwin.h>
- class CInputBlocker : public CWnd
- {
- public:
- CInputBlocker();
- virtual ~CInputBlocker();
- // create overlay as child of pParent (cover client area)
- BOOL Create(CWnd* pParent);
- // show / hide helper
- void ShowOverlay();
- void HideOverlay();
- protected:
- afx_msg BOOL OnEraseBkgnd(CDC* pDC);
- afx_msg void OnLButtonDown(UINT nFlags, CPoint point) { /* 拦截 */ }
- afx_msg void OnLButtonUp(UINT nFlags, CPoint point) { /* 拦截 */ }
- afx_msg void OnMouseMove(UINT nFlags, CPoint point) { /* 拦截 */ }
- afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { /* 拦截 */ }
- afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) { /* 拦截 */ }
- afx_msg void OnKillFocus(CWnd* pNewWnd);
- DECLARE_MESSAGE_MAP()
- private:
- CWnd* m_pParent;
- };
复制代码
2.CInputBlocker.cpp
- #include "pch.h"
- #include "CInputBlocker.h"
- BEGIN_MESSAGE_MAP(CInputBlocker, CWnd)
- ON_WM_ERASEBKGND()
- ON_WM_LBUTTONDOWN()
- ON_WM_LBUTTONUP()
- ON_WM_MOUSEMOVE()
- ON_WM_KEYDOWN()
- ON_WM_CHAR()
- ON_WM_KILLFOCUS()
- END_MESSAGE_MAP()
- CInputBlocker::CInputBlocker() : m_pParent(nullptr) {}
- CInputBlocker::~CInputBlocker() {}
- BOOL CInputBlocker::Create(CWnd* pParent)
- {
- if (!pParent) return FALSE;
- m_pParent = pParent;
- CRect rc;
- pParent->GetClientRect(&rc);
- // WS_CHILD | WS_VISIBLE | WS_TABSTOP 使其可获得焦点
- DWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_TABSTOP;
- // 注册一个临时类名(默认类也可以直接用 AfxRegisterWndClass)
- LPCTSTR pszClass = AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW);
- BOOL ok = CWnd::CreateEx(0, pszClass, _T("InputBlocker"), dwStyle, rc, pParent, 0);
- if (ok) {
- // 设置光标为等待(可选)
- ::SetCursor(::LoadCursor(NULL, IDC_WAIT));
- // 让 overlay 获得焦点并捕获鼠标
- SetFocus();
- SetCapture();
- }
- return ok;
- }
- void CInputBlocker::ShowOverlay()
- {
- if (GetSafeHwnd() == NULL) {
- Create(m_pParent);
- } else {
- ShowWindow(SW_SHOW);
- SetFocus();
- SetCapture();
- }
- }
- void CInputBlocker::HideOverlay()
- {
- if (GetSafeHwnd()) {
- if (GetCapture() == this->GetSafeHwnd()) ::ReleaseCapture();
- ShowWindow(SW_HIDE);
- // 将焦点还回父窗口
- if (m_pParent && ::IsWindow(m_pParent->GetSafeHwnd())) m_pParent->SetFocus();
- DestroyWindow();
- }
- }
- BOOL CInputBlocker::OnEraseBkgnd(CDC* pDC)
- {
- // 填充半透明颜色效果(简单灰色)
- CRect r;
- GetClientRect(&r);
- CBrush br(RGB(200,200,200)); // 可改为更深、更浅
- pDC->FillRect(&r, &br);
- return TRUE;
- }
- void CInputBlocker::OnKillFocus(CWnd* pNewWnd)
- {
- // 始终保持焦点以拦截键盘(如果需要)——可以尝试重新设置
- if (GetSafeHwnd() && ::IsWindowVisible(GetSafeHwnd())) {
- SetFocus();
- }
- }
复制代码
使用方式(在你的对话框类中):
- // CMyDialog.h
- #include "CInputBlocker.h"
- class CMyDialog : public CDialogEx
- {
- // ...
- CInputBlocker m_blocker;
- // ...
- };
- // CMyDialog.cpp
- BOOL CMyDialog::OnInitDialog()
- {
- CDialogEx::OnInitDialog();
- // 初始化时不显示覆盖层,只有在需要屏蔽时调用
- // m_blocker.Create(this); // 可提前创建,也可在需要时创建
- return TRUE;
- }
- // 在需要禁止交互时(例如启动后台任务)
- void CMyDialog::StartLongTask()
- {
- if (m_blocker.GetSafeHwnd() == NULL) {
- m_blocker.Create(this);
- }
- m_blocker.ShowWindow(SW_SHOW);
- m_blocker.SetFocus();
- m_blocker.SetCapture();
- // 启动后台线程/任务(请勿在 UI 线程做长耗时操作)
- }
- // 任务完成时调用
- void CMyDialog::EndLongTask()
- {
- m_blocker.HideOverlay();
- }
复制代码
细节和建议
- 这个覆盖层会拦截鼠标事件,因为它位于所有子控件之上并捕获鼠标;它也获取焦点从而拦截键盘事件。
- 如果你需要“半透明效果”,可以在 OnEraseBkgnd 用 GDI 绘制渐变或使用带 alpha 的位图。但实现真正的透明/半透明覆盖(alpha blending)通常需要使用顶层分层窗口(UpdateLayeredWindow),实现复杂且对 child/owner 关系要注意;上面灰色遮罩通常就足够表示“不可操作”状态。
- 覆盖层创建为子窗口,当对话框调整大小时它不会自动调整位置/大小(Create 时设置初始 size);建议在对话框的 WM_SIZE/OnSize 处理函数里调整覆盖层大小:
- void CMyDialog::OnSize(UINT nType, int cx, int cy)
- {
- CDialogEx::OnSize(nType, cx, cy);
- if (m_blocker.GetSafeHwnd()) {
- CRect rc;
- GetClientRect(&rc);
- m_blocker.MoveWindow(&rc);
- }
- }
复制代码
- 覆盖层用 `SetCapture()` 时请确保在 Hide 时 `ReleaseCapture()`(示例已处理)。
- 覆盖层不会阻止系统级快捷(如 Ctrl+Alt+Del)——但会拦截常见的鼠标/键盘交互。若需阻止 Alt+F4,请在对话框的 OnClose/OnCancel/OnOK 中检查状态并拒绝关闭(示例在前面已经给出)。
方案 B(简单但有局限)—— 弹出模态对话框(DoModal)
- 如果你的任务在 UI 线程中运行,这不合适(会阻塞线程)。如果任务在后台线程运行,则可以创建一个简单的模态对话框(“处理中...” 样式)并在后台线程执行任务,主对话框在模态对话框显示时被系统自动禁用。
- 缺点:与后台线程配合要注意线程同步与关闭对话框的方式(需通过 PostMessage/SendMessage 在 UI 线程关闭模态对话框)。
小结(推荐)
- 推荐使用覆盖层实现:不修改现有控件状态、恢复简单、用户体验好(可显示等待色块/说明)。
- 我可以直接把上面的 `CInputBlocker` 代码生成到你的项目里并帮你在对话框模版中集成(包含 resize、Start/End 调用),或者如果你想要更漂亮的半透明效果我也可以用分层窗口实现(但会稍复杂)。
你想我现在:
- 把 `CInputBlocker` 源文件直接添加到你的工程(并在一个示例对话框里集成 OnSize、StartLongTask/EndLongTask 示例);还是
- 给出一个更高级的分层(alpha)覆盖层实现?
|
|