如何在 WTL 中模拟模态对话框?
How to emulate a modal dialog in WTL?
模态对话框很好用,也很容易使用。问题是他们不允许我自己处理消息循环。所以我想我也许可以使用无模式对话框来模拟模式对话框,并且仍然自己负责消息循环以处理加速器。
目标
总的来说,我想要实现的是能够按下 Ctrl+C(和 Ctrl+Ins) 而 对话框 有焦点,然后我希望能够通过将一些信息复制到剪贴板来对此做出反应.因此,如果有人知道在 WTL 中使用模态对话框执行此操作的方法,那也可以回答我的问题。
我现在在做什么
现在我正在做的是从 CDialogImpl<T>
和 CMessageFilter
派生出我的对话 class,以便让我负责 PreTranslateMessage
。在那里,我只是使用 CAccelerator::TranslateAccelerator
和 CWindow::IsDialogMessage
来处理加速器和对话框消息。
在 OnInitDialog
中,我填充加速器 table 并将消息过滤器添加到 ("global") 消息循环中。加速器 table 与对话框本身具有相同的资源 ID:
m_accel.Attach(AtlLoadAccelerators(IDD));
CMessageLoop* pLoop = _Module.GetMessageLoop();
pLoop->AddMessageFilter(this);
然后我为 DoModal
创建了一个名为 PretendModal
的代理,它使用 "global" 消息循环。
现在我看到的效果(任务栏上出现的对话框除外)是应用程序,一旦模式对话框关闭,就无法再关闭。准确地说,主消息循环接收到 WM_QUIT
(WTL::CMessageLoop::Run() 中的 ATLTRACE2
给出了它,但在这个特技之后它仍然挂起(主框架 window 关闭,WM_QUIT 被发布,但进程不会退出)。如果我在 PretendModal
中使用单独的 CMessageLoop
(而不是 "global" 一个),整个事情的行为是一样的.
即使将 CMessageLoop
的另一个单独的新实例移动到它自己的线程中(在所有消息循环都是线程本地的之后)似乎也不能解决这个问题。这让我对我到底做错了什么感到困惑。
注意:IDCANCEL
和 IDOK
的处理程序从消息循环中删除对话框 class(即消息过滤器)。
问题
我在尝试使用无模式对话框模拟模式对话框时做错了什么?或者,我怎样才能抓住 Ctrl+C (和 Ctrl+Ins) 当使用仅从 CDialogImpl<T>
.
派生的模态对话框时
class
class CAboutDlg :
public CDialogImpl<CAboutDlg>,
public CMessageFilter
{
CAccelerator m_accel;
public:
enum { IDD = IDD_ABOUT };
BEGIN_MSG_MAP(CAboutDlg)
MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
COMMAND_ID_HANDLER(IDOK, OnCloseCmd)
COMMAND_ID_HANDLER(IDCANCEL, OnCloseCmd)
END_MSG_MAP()
virtual BOOL PreTranslateMessage(MSG* pMsg)
{
if (!m_accel.IsNull() && m_accel.TranslateAccelerator(m_hWnd, pMsg))
return TRUE;
return CWindow::IsDialogMessage(pMsg);
}
LRESULT OnInitDialog(UINT, WPARAM, LPARAM, BOOL&)
{
m_accel.Attach(AtlLoadAccelerators(IDD));
if (m_bModal)
{
CMessageLoop* pLoop = _Module.GetMessageLoop();
pLoop->AddMessageFilter(this);
}
return TRUE;
}
void PretendModal(HWND hwndParent = ::GetActiveWindow())
{
CMessageLoop* pLoop = _Module.GetMessageLoop();
if (pLoop && ::IsWindow(hwndParent))
{
HWND dlg = Create(*this);
if (::IsWindow(dlg))
{
ShowWindow(SW_SHOW);
pLoop->Run();
}
}
}
LRESULT OnCloseCmd(WORD, WORD, HWND, BOOL&)
{
if (m_bModal)
EndDialog(0);
else
{
CMessageLoop* pLoop = _Module.GetMessageLoop();
pLoop->RemoveMessageFilter(this);
::DestroyWindow(*this);
}
return 0;
}
};
所以与此同时我设法实现了我想要的。这似乎工作得很好,我还没有发现任何负面影响。
为了做我想做的事,我引入了一个 EmulateModal()
函数,它模仿了 DialogImpl
的 DoModal()
函数。
该函数如下所示:
void EmulateModal(_In_ HWND hWndParent = ::GetActiveWindow(), _In_ LPARAM dwInitParam = NULL)
{
ATLASSERT(!m_bModal);
::EnableWindow(hWndParent, FALSE);
Create(hWndParent, dwInitParam);
ShowWindow(SW_SHOW);
m_loop.AddMessageFilter(this);
m_loop.Run();
::EnableWindow(hWndParent, TRUE);
::SetForegroundWindow(hWndParent);
DestroyWindow();
}
m_loop
成员是 CDialogImpl
派生的 class 拥有的 CMessageLoop
(如问题所示,它也继承自 CMessageFilter
) .
唯一需要的其他特殊处理是将以下代码添加到监视 IDOK
和 IDCANCEL
的命令 ID 处理程序(在我的例子中,它们都是为了关闭对话框),即在 OnCloseCmd
.
内
if(m_bModal)
{
EndDialog(wID);
}
else
{
m_loop.RemoveMessageFilter(this);
PostMessage(WM_QUIT);
}
在调用 DestroyWindow()
之前 从消息循环中删除消息过滤器(即 PreTranslateMessage
)很重要。 非常重要 退出 "inner" 消息循环,该消息循环由 CDialogImpl
-派生的 class 拥有并且 Run()
是从上面的 EmulateModal()
调用。
所以要点是这样的:
- 从我的问题中删除
PretendModal()
方法
- 使用 "inner" 消息循环而不是使用顶级消息循环
模态对话框很好用,也很容易使用。问题是他们不允许我自己处理消息循环。所以我想我也许可以使用无模式对话框来模拟模式对话框,并且仍然自己负责消息循环以处理加速器。
目标
总的来说,我想要实现的是能够按下 Ctrl+C(和 Ctrl+Ins) 而 对话框 有焦点,然后我希望能够通过将一些信息复制到剪贴板来对此做出反应.因此,如果有人知道在 WTL 中使用模态对话框执行此操作的方法,那也可以回答我的问题。
我现在在做什么
现在我正在做的是从 CDialogImpl<T>
和 CMessageFilter
派生出我的对话 class,以便让我负责 PreTranslateMessage
。在那里,我只是使用 CAccelerator::TranslateAccelerator
和 CWindow::IsDialogMessage
来处理加速器和对话框消息。
在 OnInitDialog
中,我填充加速器 table 并将消息过滤器添加到 ("global") 消息循环中。加速器 table 与对话框本身具有相同的资源 ID:
m_accel.Attach(AtlLoadAccelerators(IDD));
CMessageLoop* pLoop = _Module.GetMessageLoop();
pLoop->AddMessageFilter(this);
然后我为 DoModal
创建了一个名为 PretendModal
的代理,它使用 "global" 消息循环。
现在我看到的效果(任务栏上出现的对话框除外)是应用程序,一旦模式对话框关闭,就无法再关闭。准确地说,主消息循环接收到 WM_QUIT
(WTL::CMessageLoop::Run() 中的 ATLTRACE2
给出了它,但在这个特技之后它仍然挂起(主框架 window 关闭,WM_QUIT 被发布,但进程不会退出)。如果我在 PretendModal
中使用单独的 CMessageLoop
(而不是 "global" 一个),整个事情的行为是一样的.
即使将 CMessageLoop
的另一个单独的新实例移动到它自己的线程中(在所有消息循环都是线程本地的之后)似乎也不能解决这个问题。这让我对我到底做错了什么感到困惑。
注意:IDCANCEL
和 IDOK
的处理程序从消息循环中删除对话框 class(即消息过滤器)。
问题
我在尝试使用无模式对话框模拟模式对话框时做错了什么?或者,我怎样才能抓住 Ctrl+C (和 Ctrl+Ins) 当使用仅从 CDialogImpl<T>
.
class
class CAboutDlg :
public CDialogImpl<CAboutDlg>,
public CMessageFilter
{
CAccelerator m_accel;
public:
enum { IDD = IDD_ABOUT };
BEGIN_MSG_MAP(CAboutDlg)
MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
COMMAND_ID_HANDLER(IDOK, OnCloseCmd)
COMMAND_ID_HANDLER(IDCANCEL, OnCloseCmd)
END_MSG_MAP()
virtual BOOL PreTranslateMessage(MSG* pMsg)
{
if (!m_accel.IsNull() && m_accel.TranslateAccelerator(m_hWnd, pMsg))
return TRUE;
return CWindow::IsDialogMessage(pMsg);
}
LRESULT OnInitDialog(UINT, WPARAM, LPARAM, BOOL&)
{
m_accel.Attach(AtlLoadAccelerators(IDD));
if (m_bModal)
{
CMessageLoop* pLoop = _Module.GetMessageLoop();
pLoop->AddMessageFilter(this);
}
return TRUE;
}
void PretendModal(HWND hwndParent = ::GetActiveWindow())
{
CMessageLoop* pLoop = _Module.GetMessageLoop();
if (pLoop && ::IsWindow(hwndParent))
{
HWND dlg = Create(*this);
if (::IsWindow(dlg))
{
ShowWindow(SW_SHOW);
pLoop->Run();
}
}
}
LRESULT OnCloseCmd(WORD, WORD, HWND, BOOL&)
{
if (m_bModal)
EndDialog(0);
else
{
CMessageLoop* pLoop = _Module.GetMessageLoop();
pLoop->RemoveMessageFilter(this);
::DestroyWindow(*this);
}
return 0;
}
};
所以与此同时我设法实现了我想要的。这似乎工作得很好,我还没有发现任何负面影响。
为了做我想做的事,我引入了一个 EmulateModal()
函数,它模仿了 DialogImpl
的 DoModal()
函数。
该函数如下所示:
void EmulateModal(_In_ HWND hWndParent = ::GetActiveWindow(), _In_ LPARAM dwInitParam = NULL)
{
ATLASSERT(!m_bModal);
::EnableWindow(hWndParent, FALSE);
Create(hWndParent, dwInitParam);
ShowWindow(SW_SHOW);
m_loop.AddMessageFilter(this);
m_loop.Run();
::EnableWindow(hWndParent, TRUE);
::SetForegroundWindow(hWndParent);
DestroyWindow();
}
m_loop
成员是 CDialogImpl
派生的 class 拥有的 CMessageLoop
(如问题所示,它也继承自 CMessageFilter
) .
唯一需要的其他特殊处理是将以下代码添加到监视 IDOK
和 IDCANCEL
的命令 ID 处理程序(在我的例子中,它们都是为了关闭对话框),即在 OnCloseCmd
.
if(m_bModal)
{
EndDialog(wID);
}
else
{
m_loop.RemoveMessageFilter(this);
PostMessage(WM_QUIT);
}
在调用 DestroyWindow()
之前 从消息循环中删除消息过滤器(即 PreTranslateMessage
)很重要。 非常重要 退出 "inner" 消息循环,该消息循环由 CDialogImpl
-派生的 class 拥有并且 Run()
是从上面的 EmulateModal()
调用。
所以要点是这样的:
- 从我的问题中删除
PretendModal()
方法 - 使用 "inner" 消息循环而不是使用顶级消息循环