Modal dialogs are nice and easy to use. Problem is that they don't allow me to handle the message loop myself. So I thought I could perhaps use a modeless dialog to emulate a modal one and still be in charge of the message loop myself in order to handle accelerators.
What I want to achieve in general is the ability to press Ctrl+C (and Ctrl+Ins) while the dialog has the focus and then I want to be able to react to that by copying some information into the clipboard. So if anyone knows a way to do that with modal dialogs in WTL, that also would answer my question.
Now what I currently do is deriving my dialog class from CDialogImpl<T>
and CMessageFilter
in order to put me in charge of PreTranslateMessage
. In there I simply use CAccelerator::TranslateAccelerator
and CWindow::IsDialogMessage
to process accelerators and dialog box messages.
In OnInitDialog
I populate the accelerator table and add the message filter to the ("global") message loop. The accelerator table has the same resource ID as the dialog itself:
m_accel.Attach(AtlLoadAccelerators(IDD));
CMessageLoop* pLoop = _Module.GetMessageLoop();
pLoop->AddMessageFilter(this);
Then I created a surrogate for DoModal
by the name PretendModal
which uses the "global" message loop.
Now the effect (other than the dialog appearing on the task bar) that I am seeing is that the application, once the modal dialog gets closed, cannot be closed anymore. To be precise, the main message loop receives WM_QUIT
(the ATLTRACE2
in WTL::CMessageLoop::Run() gives that away, but it still hangs after this stunt (main frame window gets closed, WM_QUIT gets posted, but the process does not exit). The whole thing behaves the same if I use a separate CMessageLoop
inside PretendModal
(instead of the "global" one).
Even moving another separate new instance of CMessageLoop
into its own thread (after all message loops are thread-local) does not seem to resolve this issue. This leaves me puzzled as to what exactly I am doing wrong here.
NB: The handler for IDCANCEL
and IDOK
removes the dialog class (i.e. the message filter) from the message loop.
What am I doing wrong in my attempt to emulate a modal dialog using a modeless one? Alternatively, how can I catch Ctrl+C (and Ctrl+Ins) when using a modal dialog derived just from CDialogImpl<T>
.
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;
}
};
So meanwhile I managed to achieve what I wanted. This seems to be working nicely and I haven't found any negative side effects as of yet.
In order to do what I want, I introduced an EmulateModal()
function which kind of mimics the DoModal()
function of DialogImpl
.
That function looks as follows:
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();
}
The m_loop
member is a CMessageLoop
owned by the CDialogImpl
-derived class (which also inherits from CMessageFilter
as shown in the question).
The only other special handling that is needed, was to add the following code to the command ID handler which watches for IDOK
and IDCANCEL
(which in my case are both meant to close the dialog), i.e. inside OnCloseCmd
.
if(m_bModal)
{
EndDialog(wID);
}
else
{
m_loop.RemoveMessageFilter(this);
PostMessage(WM_QUIT);
}
It is important to remove the message filter (i.e. PreTranslateMessage
) from the message loop prior to calling DestroyWindow()
. It is also very important to exit the "inner" message loop which is owned by the CDialogImpl
-derived class and whose Run()
is being called from EmulateModal()
above.
So the gist is this:
PretendModal()
method from my question