Click here to Skip to main content
Click here to Skip to main content

Tutorial - Modeless Dialogs with MFC

By , 18 Jun 2002
 

Introduction

Modeless dialog boxes have often puzzled newbie programmers. Basically a modeless dialog box is one that allows us to interact with other windows even when the modeless dialog is still on screen. If you keep in mind a few nifty little tricks then programming modeless dialogs will be a piece of cake.

Creating the modeless dialog

The straightforward way to create a modeless dialog is using Create(). Pass the name of the dialog's template resource and an optional CWnd* which points to the parent window. If you don't pass a parent window pointer the main application window will be used as the parent window. Create() will return true if the call was successful. 

Since Create() returns immediately unlike DoModal(), you must never declare your modeless dialog as a local variable with scope and lifetime only within the function where it is declared. Instead allocate the modeless dialog on the heap. If you don't do that, the modeless dialog will be destroyed the moment, you exit the function within which you declared it.

An alternative solution is to declare your modeless dialog as a heap member object of your main frame window or your CWinApp derived class. An advantage with this method is that you actually have control over the modeless dialog, since you have a pointer to it.

By the way, unlike modal dialogs, modeless dialogs need to have the WS_VISIBLE style set if you want them to be visible immediately after creation. Otherwise you'll have to explicitly call ShowWindow() with SW_SHOW. In fact I recommend that you do this, instead of going all over the place, changing default styles.

CModeLess *m_pmodeless = new CModeLess(this);
m_pmodeless->Create(CModeLess::IDD);
m_pmodeless->ShowWindow(SW_SHOW);	

The parent issue

The usual practice is to make the parent window the main window of your application, which is typically the main frame window. Now one issue with this is that the modeless dialog will remain on top of this parent window. It allows you to interact with the main frame window, perhaps it contains a CView derived view. But it may be annoying and undesirable to have the modeless dialog remain on top. The solution here is to create the modeless dialog as a child of the desktop. Use GetDesktopWindow() to get a pointer to the Desktop and pass that as the parent window for the modeless dialog in your call to Create().

m_pmodeless->Create(CModeLess::IDD,GetDesktopWindow());

Destroying the modeless dialog

Since we have allocated memory on the heap, we must delete it when the modeless dialog is destroyed, otherwise we'll soon run into big trouble with memory leaking left, right and center. When the dialog is destroyed the last message our handler class receives is the WM_NCDESTROY message. The OnNcDestroy function is invoked and this in turns calls the virtual function PostNcDestroy. That's exactly where we can delete our modeless dialog. First call the base class function so that it does it's own cleaning up.

void CModeLess::PostNcDestroy() 
{	
    CDialog::PostNcDestroy();
    delete this;
}

Issue with member objects

If the modeless dialog is a member object of the parent window class, we have a slight issue here. The member variable still holds a pointer reference, but the memory it references has been deleted. There are workarounds to this problem. One method is to post an user defined message to the parent window and handle it in the parent class, by setting the modeless dialog member variable to NULL. Another method is to use GetParent() to get the parent window, if any and then cast it to the actual parent class. Now we have access to the parent class's member variable that holds the pointer to the modeless dialog. Set that to NULL. The latter method is portrayed later where I discuss how to restrict a modeless dialog to one instance. The former method is shown below :-

void CModeLess::PostNcDestroy() 
{	
    CDialog::PostNcDestroy();
    GetParent()->PostMessage(WM_MODELESS_CLOSED,0,0);
    delete this;
}
LRESULT CMainFrame::OnMyMethod(WPARAM wParam, LPARAM lParam)
{
    m_pmodeless = NULL; 
    return 0;
}

Problems with OnOK() and OnCancel()

In modal dialog boxes, everybody including the queen's cook, has the OK/Cancel buttons. In my opinion, and presumably in many other more learned people's opinions, you'd do good to avoid having OK and Cancel on a modeless dialog. But if for some unavoidable reason, you badly want to have them on your modeless dialog, then you'll need to over-ride both functions.

Here is my modeless version of the OnCancel() function. As you can see I have simply called DestroyWindow() and I haven't bothered to call the base class. In fact don't call the base class at all. The base class function will call EndDialog() which is associated with DoModal().

void CModeLess::OnCancel() 
{
    DestroyWindow();
}

Okay, now for my modeless version of OnOK(). I have called DestroyWindow() as in the OnCancel(), but there is some extra code too as you can see. I am calling UpdateData, because that's what OnOK() does in a modal dialog. If the DDV macro validations are successful then UpdateData(true) returns true and we destroy the window, else the DDV message box is automatically shown to the user and we refuse to destroy the dialog. Thus we are simulating the behavior of a modal dialog's OK button here.

void CModeLess::OnOK() 
{
    if(UpdateData(true))
    {
        DestroyWindow();	
    }

}

Passing back data

In modal dialogs, we can still access the data variables when DoModal() returns because the dialog object has not been destroyed yet, only the underlying dialog window has been destroyed. This is also possible with modeless dialogs using a nifty trick as shown below.

void CModeLess::OnOK() 
{
    if(UpdateData(true))
    {
        ((CMainFrame*)m_parent)->m_x=m_sss;
        DestroyWindow();	
    }

}

Here I have assigned the value of the dialog data variable m_sss to the parent class's member variable, m_x. Here, m_parent is a pointer to the parent window. If you are wondering where I got this m_parent from, scroll up and see how I have constructed my modeless dialog object. I'll repeat that single line to refresh your memory, and also to help you avoid scrolling, thus saving you some energy.

CModeLess *m_pmodeless = new CModeLess(this);

As you can see, I have passed this to the constructor. In my case, this is a pointer to my CFrameWnd derived class which App Wizard has named as CMainFrame for me. Now take a look at my CModeLess class's constructor.

CModeLess::CModeLess(CWnd* pParent /*=NULL*/)
    : CDialog(CModeLess::IDD, pParent)
{
    //{{AFX_DATA_INIT(CModeLess)
    m_sss = 0;
    //}}AFX_DATA_INIT
    m_parent=pParent; //This is where I point m_parent to my main frame window
}

It all slowly makes sense, eh?

Tracking the modeless dialog count

Let's say you want to have only one instance of the modeless dialog alive at one time. In that case, each time the user initiates some action that results in the bringing up of the modeless dialog you have to check and see if the modeless dialog is already active. Say, m_pmodeless is the modeless dialog member of your class. In the class constructor set m_pmodeless to NULL. Now each time you check to see if m_pmodeless  is NULL and if it is NULL, create a new modeless dialog, otherwise, show a MessageBox that the dialog is already active or use SetForegroundWindow() to bring the modeless dialog to the foreground..

Here is how I create my modeless dialog now that I want to restrict them to just one at a time:-

if(m_pmodeless)
{
    m_pmodeless->SetForegroundWindow();
}
else
{		
    m_pmodeless = new CModeLess(this);
    m_pmodeless->Create(CModeLess::IDD);
    m_pmodeless->ShowWindow(SW_SHOW);	
}

But when the dialog is destroyed we need to inform the parent class that the pointer it holds is now useless. What we do is to set that pointer to NULL in the PostNcDestroy. In fact it is essential that you do this, otherwise the next time the user tries to activate the modeless dialog, your program will crash as it thinks m_pmodeless is still pointing to a valid dialog window and tried to call SetForegroundWindow() on it. And here is my PostNcDestroy :-

void CModeLess::PostNcDestroy() 
{	
    CDialog::PostNcDestroy();
    ((CMainFrame*)m_parent)->m_pmodeless = NULL;
    delete this;
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Nish Sivakumar
United States United States
Member
Nish is a real nice guy who has been writing code since 1990 when he first got his hands on an 8088 with 640 KB RAM. Originally from sunny Trivandrum in India, he has been living in various places over the past few years and often thinks it’s time he settled down somewhere.
 
Nish has been a Microsoft Visual C++ MVP since October, 2002 - awfully nice of Microsoft, he thinks. He maintains an MVP tips and tricks web site - www.voidnish.com where you can find a consolidated list of his articles, writings and ideas on VC++, MFC, .NET and C++/CLI. Oh, and you might want to check out his blog on C++/CLI, MFC, .NET and a lot of other stuff - blog.voidnish.com.
 
Nish loves reading Science Fiction, P G Wodehouse and Agatha Christie, and also fancies himself to be a decent writer of sorts. He has authored a romantic comedy Summer Love and Some more Cricket as well as a programming book – Extending MFC applications with the .NET Framework.
 
Nish's latest book C++/CLI in Action published by Manning Publications is now available for purchase. You can read more about the book on his blog.
 
Despite his wife's attempts to get him into cooking, his best effort so far has been a badly done omelette. Some day, he hopes to be a good cook, and to cook a tasty dinner for his wife.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
Generalthanks... but i have a question..memberVirex_A12 Nov '08 - 21:30 
Thanks for your article, I was able to create and call a modeless window.. but i have a problem..
 
first problem is that, my modeless window does not become active.. when i press an ok button, the modeless window is displayed at the background... if there are other open windows present in the desktop, then i cannot see the modeless window.. then when i press the modeless window, the title bar is still light colored.. meaning, its not active.. but the thing is that, I can press the button and do the functions in that modeless windo even if it is not active.......
 
my question is, whats wrong with my program?? this is a code from which i read in your article..
 
CVideoControlDlg* VCDlg = new CVideoControlDlg(this);
VCDlg->Create( CVideoControlDlg::IDD, GetDesktopWindow());
VCDlg->ShowWindow(SW_SHOW);
 
in addition, how can i call the main window back from the child window?? for example, i pushed a close button in the modeless window, how can i call the parent window???
 
please do reply... thanks..
GeneralModeless dialog not showing the fieldsmemberpvasanthi9 Jul '07 - 2:09 
Hi,
 
I have many libaries and about 600 executables written in C++. For the work I am doing, I am only looking at changing one of the libaries. The code is written using Visual C++ on a windows PC. But productionized on IBM mainframe. But, for the purpose of developer testing and debugging, we have got few classes subclassed from the main classes in that library and these Windows specific classes will be used if a particular compiler setting is used.
 
As this libary (written long time back by someone else) currently stands, each program will add fields to a modal dialog resource at run time. When the dialog shows, user will enter some details in the edit fields and press "Enter". When running on Windows, on the user pressing "Enter" key, just before the dialog class calls the CDialog::EndDialog, it captures the screen shot of this dialog and brings up a pop-up window and called the CDialog::EndDialog. The libary then does work with the user's input, validates that input and depending on which program, does different things. But, if the validation fails, then it will bring up the original modal dialog with a message saying what was invalid along the last field. If the validation has been successful, it will create a new process for another program and once that new program's window comes up, the first program will exit.
 
Need for change - Displaying the screen shot is not useful, as users want menu items on the dialogs. I can't give menu items on a screen shot. So, I decided to create a modeless dialog (using the same dialog resource as the modal dialog) instead of using the screen shot. This modeless dialog will be populated with the same fields as the originial modal dialog has. Now, when creating the modeless dialog, if I pass the modal dialog class object as the parent of the modeless dialog, then when the modal dialog is ended, the modeless dialog is also deleted. Therefore, when creating the modeless dialog object, I am passing the DesktopWindow as the parent.
 
When I run the program, I get the modeless dialog shown without showing the fields. So, I commented out the code that will destroy the modeless dialog when the modal dialog appears again. When I run the application, modal dialog appears, user enters details and presses "Enter", modeless dialog appears without fields being shown, modal dialog disappears, validation takes place, the modal dialog appears again and NOW, the fields are populated in the modeless dialog.
 
Debugging to find out why the fields were not showing did not give me any information. So, I changed the modeless dialog. This time, I created a new dialog resource with a static text field saying "Processing". Instead of the previous modeless dialog, now the modal dialog class object will create the new modeless dialog. Again, the static text was not displayed until the modal dialog appeared for the second time.
 
Any clue on why the modeless dialog does not show the static text (and my original modeless dialog does not show the fields) when it is shown?
 
Thank you.
 

 
Rgds,
pvasanthi
GeneralRe: Modeless dialog not showing the fieldsmemberSpjoiing2 Jul '10 - 2:58 
Hi.
I had the same problem as you with only 1 line of static text in the dialog box.
 
Found the solution after some googling.
 
The modeless dialogbox starts a new thread, and the update of it might be stopped if it doesn't get enough processor time. Add a call to UpdateWindow() just after ShowWindow() and the fields will be shown.. At least my text was.
 
My code became something like this.
 
CDialog dlg;
dlg.Create(IDD_INITIALIZING, this);
dlg.ShowWindow(SH_SHOW);
dlg.UpdateWindow();
 
//....Do some other code......
 
dlg.DestroyWindow();
 

Regards
Roar.
QuestionRe: Modeless dialog not showing the fieldsmemberp.divya1126 May '11 - 6:40 
hi,
 
i have a dialog box called parent with a button in the dialog..on click of the button i need to open a modeless dialog..
the modeless dialog has a edit box in it..on click of that button which is there in parent, a message "hello" needs to be displayed in the edit box of modeless dialog..i dont know how to exactly write into edit box of the modeless dialog..
can someone pls help as soon as possible as i need to use it in my project..
thanks in advance..
QuestionStorage of Modeless dialog is it in Heap /StackmemberPrabodh Kumar Panda18 Jun '07 - 21:57 
As far i am conserned it must be storing in heap, is their any possiblity to store a modeless dialog in stack memory
 
Prabodh
AnswerRe: Storage of Modeless dialog is it in Heap /StackmvpNishant Sivakumar24 Oct '11 - 8:09 
You can make it a member variable of a class whose instance will be alive long enough for you to use the modeless dialog.
Regards,
Nish
My technology blog: voidnish.wordpress.com
 
Part 2 in my WinRT/C++ series : Visual C++ and WinRT/Metro - Databinding Basics

GeneralNice, but not the easiest methodmemberMubeen_D29 Nov '06 - 6:34 
I read the article above, it's nice, but it's definately not the the easiest method of achieving a modeless dialog box.
 
In your method you have to override a virtual function, post a message from the dialog to the parent, and then respond to a posted message inside of the parent. Too many steps to be called easy.
 
Here is a simplier approach:
STEP 1: ParentClass.h --> add member variable to modeless dialog
STEP 2: ParentClass.cpp::OnInitDialog()
{
...
m_myModeless.Create(CModelessclass.IDD,NULL);
}
STEP 3: SomeOnButtonShowDialog()
{ // this will ALWAYS work!
m_myModeless.ShowWindow(SW_NORMAL);
}
 
Additional Suggestions for the modeless dialog:
Don't actually close the dialogbox when you click "OK", just hide the window! When the parent needs to show it again, invariably it will call the same function to show the dialogbox - in otherwords, the parent will use SomeOnButtonShowDialog()
 
When the application ends, the parent can just call a function in the modeless dialogbox to call the dialog's real OnOK();
 
So maybe add a STEP 4: Modeless Dialog::OnRealOK()
{
CDialog::OnOK();
}
 
Maybe it sounds like the same number of steps; but the implementation is a no-brainer. No memory allocation problems, no odd virtual functions to worry about; and no need to recreate the same dialogbox over and over again!
GeneralRe: Nice, but not the easiest method [modified]memberrbid14 Sep '08 - 2:37 
True,
 
I this method due that it is much simple Smile | :)
The only problem is when you want to use a modeless dialog based on a
sub-class of CDialog, (E.g. CResizableDialog[^]) then you get a compiler complain:
Warning: calling DestroyWindow in CWnd::~CWnd; OnDestroy or PostNcDestroy in derived class will not be called.
Warning: calling DestroyWindow in CDialog::~CDialog --
	OnDestroy or PostNcDestroy in derived class will not be called.
 
The problem above occurs only if you want the Modeless Dialog parent to be the DesktopWindow instead of your application window:
    . . .
    m_regsDlg.Create(IDD_REGISTERS_DIALOG, GetDesktopWindow());
    . . .
 
Do you know how to overcome to this complain?
 
Thanks.
 
-- Ricky Marek (AKA: rbid)
-- "Things are only impossible until they are not" --- Jean-Luc Picard
 
My articles
 
modified on Sunday, September 14, 2008 9:11 AM

QuestionGetDesktopWindowmembereusto11 Jul '06 - 0:11 
I'm trying to place that dialog on the MainFrame of a MDI app.
The problem is that if i set the parrent of the CDialog to the MainFrame or NULL it will stay on top of all other Doc/View/Frame itemsFrown | :(
 
I tried to use GetDesktopWindow but the app crashes with "Acces violation".
Any poiters? How do i fix this? Is this aproach wrong all together?

QuestionHow to change parent of dialog wthout destroymembersrikanth_shettigar22 May '06 - 2:54 
Hi,
 
i wanted to change the dialog's parent by calling setparent.but there is paint problem...i tried setowner also...i don't want to destroy for changing parent...please help me.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130523.1 | Last Updated 19 Jun 2002
Article Copyright 2001 by Nish Sivakumar
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid