如何在 WTL 中模拟模态对话框?

How to emulate a modal dialog in WTL?

模态对话框很好用,也很容易使用。问题是他们不允许我自己处理消息循环。所以我想我也许可以使用无模式对话框来模拟模式对话框,并且仍然自己负责消息循环以处理加速器。

目标

总的来说,我想要实现的是能够按下 Ctrl+C(和 Ctrl+Ins) 而 对话框 有焦点,然后我希望能够通过将一些信息复制到剪贴板来对此做出反应.因此,如果有人知道在 WTL 中使用模态对话框执行此操作的方法,那也可以回答我的问题。

我现在在做什么

现在我正在做的是从 CDialogImpl<T>CMessageFilter 派生出我的对话 class,以便让我负责 PreTranslateMessage。在那里,我只是使用 CAccelerator::TranslateAcceleratorCWindow::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 的另一个单独的新实例移动到它自己的线程中(在所有消息循环都是线程本地的之后)似乎也不能解决这个问题。这让我对我到底做错了什么感到困惑。

注意:IDCANCELIDOK 的处理程序从消息循环中删除对话框 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() 函数,它模仿了 DialogImplDoModal() 函数。

该函数如下所示:

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 ) .

唯一需要的其他特殊处理是将以下代码添加到监视 IDOKIDCANCEL 的命令 ID 处理程序(在我的例子中,它们都是为了关闭对话框),即在 OnCloseCmd.

if(m_bModal)
{
    EndDialog(wID);
}
else
{
    m_loop.RemoveMessageFilter(this);
    PostMessage(WM_QUIT);
}

在调用 DestroyWindow() 之前 从消息循环中删除消息过滤器(即 PreTranslateMessage)很重要。 非常重要 退出 "inner" 消息循环,该消息循环由 CDialogImpl-派生的 class 拥有并且 Run() 是从上面的 EmulateModal() 调用。

所以要点是这样的:

  1. 从我的问题中删除 PretendModal() 方法
  2. 使用 "inner" 消息循环而不是使用顶级消息循环