Introduction
This article presents a method for easily managing modeless dialogs.
Functionality includes:
- Determining if the dialog is already created
- Closing the dialog automatically when the parent object is destroyed
- Notifying the parent object when the dialog is closed
- Accessing functions in the dialog from the parent object.
The concept is based on a tracker object that exists in the dialog's parent and
a helper class from which the dialog is dual-derived. The helper class
automates the interaction between the dialog and its tracker, and the tracker
allows the parent to manage the dialog.
The term "parent" is used to represent
the class that creates the modeless dialog and is therefore responsible for
keeping an eye on it. The parent is not necessarily a parent window.
It could be any object that needs to have a modeless dialog.
Background
Dealing with 1 or 2 modeless dialogs is not difficult. You track a
pointer to the dialog, make sure the dialog clears the pointer or messages the
parent when it closes, check if a dialog is already posted before
re-instantiating the class, etc, etc. These simple steps present many
opportunities for mistakes, omissions, and general confusion. This class
was designed to wrap those details and provide a standard way of dealing with
modeless dialogs floating around in an application.
Many applications avoid
modeless dialogs. This keeps things simple when modeless functionality is not needed.
The complexity of modeless dialogs is analogous to multi-threading in that it
requires management of multiple independent processes.
Often, however, in robot
control systems, measurement systems or other hardware control applications, it is necessary
to monitor many things simultaneously and provide user input where needed.
Modeless dialogs are a perfect match for these cases.
Using the code
These steps are briefly described in the ModelessDialogTracker.h file:
Step 1
Add a ModelessDialogHelper
derivation to your
wizard-generated dialog class. Note the block of "modeless dialog specific" functions
in this header file. These
are useful for modeless dialogs, but not essential for the task at hand.
It is, however, a useful block of code to paste into any dialog that is on its
way to becoming modeless.
#include "ModelessDialogTracker.h"
class DialogA : public CDialog, ModelessDialogHelper
{
DECLARE_DYNAMIC(DialogA)
public:
DialogA(ModelessDialogTracker& tracker);
BOOL Create(UINT nID, CWnd* pWnd)
{ return CDialog::Create(nID,pWnd); }
void PostNcDestroy()
{ delete this; }
void OnCancel()
{ DestroyWindow(); }
virtual ~DialogA();
enum { IDD = IDD_DIALOG1 };
protected:
virtual void DoDataExchange(CDataExchange* pDX);
DECLARE_MESSAGE_MAP()
};
The ModelessDialogHelper
class simply constructs and destructs with the
dialog class since it is in its derivation chain. At construction and
destruction time, the ModelessDialogTracker
is notified of the change in status.
Step 2
Enhance the constructor of the wizard-generated dialog
constructor (in the dialog .cpp file):
DialogA::DialogA(ModelessDialogTracker& tracker)
: ModelessDialogHelper(tracker, *this)
{
}
Step 3
Instantiate a ModelessDialogTracker
object in the
"parent" class (or any class that is to be responsible for the dialog's
existence):
ModelessDialogTracker trackA;
Step 4
Pop your dialog using the Create()
call. Remember to set
the Visible property of the dialog to TRUE in the resource editor (if you don't,
you will post and invisible dialog -- not too impressive). Note
the check to see if it is already popped. That call will SetActiveWindow()
on the dialog if it is already up. Also, notice the passing of the dialog
tracker to the DialogA constructor. This is needed so the helper can
establish a link to the tracker class. Since that link is established,
there is no need to keep track of pnewDlg
after the dialog is up.
#include "DialogA.h"
void CModelessDialogTrackerDemoDlg::OnPopA()
{
if (trackA.IsAlreadyPopped())
return;
DialogA* pnewdlg = new DialogA(trackA);
pnewdlg->Create(pnewdlg->IDD,NULL);
}
That's it for basic usage. You can pop the dialog, and the parent will
keep track of the state of the dialog. When the ModelessDialogTracker
object, trackA, is destroyed, it will automatically close and cleanup the
modeless dialog if necessary.
Additional usage
Forcible Close
Should you need to forcibly close the modeless dialog from the parent, you can just call CloseDialog()
:
trackA.CloseDialog();
Calling Dlg Functions
If you want to call a function in the dialog (to update its contents or
whatever), you can use the GetDlg()
member of the tracker class and
cast the return to the proper dialog type: (see DialogB in the demo
project.)
DialogB& dlg = (DialogB&)*trackB.GetDlg();
dlg.SomeFunctionInTheDialog(necessaryData);
Notification of Close
If you need to be notified, or take some action when the dialog is closed,
you can simply derive your own class from ModelessDialogTracker
and override the OnDialogClosed()
member function:
class TrackDialogC : public ModelessDialogTracker
{
public:
...
virtual void OnDialogClosed();
...
};
Then, instantiate your class (TrackDialogC
) instead of ModelessDialogTracker
, and use it to
track the dialog. When the dialog closes, the virtual function is called and you
can take whatever action is necessary. (See DialogC in the demo project.)
Demo Project
The demo project is dialog based and allows you to test-drive 3 implementations
of this class:
- DialogA - basic implementation.
- DialogB - shows the parent calling functions in the modeless dialog.
- DialogC - implements a derived dialog tracker class to update the dialog
status in the parent.
Points of Interest
You'll notice a warning override in the ModelessDialogTracker.h file:
#pragma warning( disable : 4355 )
This suppresses the following compiler warning:
DialogA.cpp(13) : warning C4355: 'this' : used in
base member initializer list
This is caused by the implementation of the DialogA constructor preamble where we pass "this", the dialog itself, into
one of the base classes,
ModelessDialogHelper
's, constructor:
DialogA::DialogA(ModelessDialogTracker& tracker)
: ModelessDialogHelper(tracker, *this)
{
}
In this case, we want the helper class to have the pointer to the CDialog
derived class so it can call the appropriate functions. The warning is due
to the fact that a base class should not need a pointer to a class lower in the
derivation chain. In the case of dual-derivation, this is not so.
Thus the passing of the pointer. It bridges the gap between the helper
class and the dialog itself since they are at the same level of the derivation
hierarchy. For this reason, we did the unthinkable and stomped out the warning.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.