modeless dialog WM_CLOSE - opinion
-
Need your thoughts and/or opinion. Question at the bottom. When user closes the modeless dialog box you can trap the WM_CLOSE message to insure proper cleanup. According to Help CWnd::OnClose The framework calls this member function as a signal that the CWnd or an application is to terminate. The default implementation calls DestroyWindow. I found two ways to approach the user who closes the modeless dialog box by clicking the x on the upper right hand corner of the window Looking at the first example below. I found out that if you don't call DestroyWindow() from your OnClose() and you don't want multiple instances of your modeless opened you will need to set the pointer to NULL here (otherwise your pointer value can't be used as a flag to prohibit other instances of your modeless dialog from being opened). PostNcDestroy apparently isn't run until the parent is destroyed. Once the parent/owner is closed then a PostNcDestroy is run for each modeless that opened - one right after another. void CDialogDerived::OnClose() { CDialog::OnClose(); m_pParent->m_pModelessDialog = NULL; } void CDialogDerived::PostNcDestroy() { CDialog::PostNcDestroy(); m_pParent->m_pModelessDialog = NULL; //in case destroy window is called from your code and OnClose is never run delete this; } Example 2. This seems like a cleaner approach as the Dialog is destroyed immediately and thus your pointer is set to NULL immediately void CDialogDerived::OnClose() { CDialog::OnClose(); DestroyWindow(); } void CDialogDerived::PostNcDestroy() { CDialog::PostNcDestroy(); m_pParent->m_pModelessDialog = NULL; delete this; } Any thoughts pro and con to either of these approaches? I'm curious why the PostNcDestroy is not run promptly with the OnClose call on the first example. Thanks!
-
Need your thoughts and/or opinion. Question at the bottom. When user closes the modeless dialog box you can trap the WM_CLOSE message to insure proper cleanup. According to Help CWnd::OnClose The framework calls this member function as a signal that the CWnd or an application is to terminate. The default implementation calls DestroyWindow. I found two ways to approach the user who closes the modeless dialog box by clicking the x on the upper right hand corner of the window Looking at the first example below. I found out that if you don't call DestroyWindow() from your OnClose() and you don't want multiple instances of your modeless opened you will need to set the pointer to NULL here (otherwise your pointer value can't be used as a flag to prohibit other instances of your modeless dialog from being opened). PostNcDestroy apparently isn't run until the parent is destroyed. Once the parent/owner is closed then a PostNcDestroy is run for each modeless that opened - one right after another. void CDialogDerived::OnClose() { CDialog::OnClose(); m_pParent->m_pModelessDialog = NULL; } void CDialogDerived::PostNcDestroy() { CDialog::PostNcDestroy(); m_pParent->m_pModelessDialog = NULL; //in case destroy window is called from your code and OnClose is never run delete this; } Example 2. This seems like a cleaner approach as the Dialog is destroyed immediately and thus your pointer is set to NULL immediately void CDialogDerived::OnClose() { CDialog::OnClose(); DestroyWindow(); } void CDialogDerived::PostNcDestroy() { CDialog::PostNcDestroy(); m_pParent->m_pModelessDialog = NULL; delete this; } Any thoughts pro and con to either of these approaches? I'm curious why the PostNcDestroy is not run promptly with the OnClose call on the first example. Thanks!
Why make this so complex? You can easily check if the window still exists from the parent with a check like this:
if ( NULL == m_pModelessDialog->GetSafeHwnd() )
{
if ( NULL != m_pModelessDialog )
delete m_pModelessDialog;
// create new instance of dialog
// ...
}And just forget about all the messy cleanup in the dialog itself.
---- Scripts i've known... CPhog 0.9.9 - make CP better. Forum Bookmark 0.2.5 - bookmark forum posts on Pensieve Print forum 0.1.1 - printer-friendly forums
-
Need your thoughts and/or opinion. Question at the bottom. When user closes the modeless dialog box you can trap the WM_CLOSE message to insure proper cleanup. According to Help CWnd::OnClose The framework calls this member function as a signal that the CWnd or an application is to terminate. The default implementation calls DestroyWindow. I found two ways to approach the user who closes the modeless dialog box by clicking the x on the upper right hand corner of the window Looking at the first example below. I found out that if you don't call DestroyWindow() from your OnClose() and you don't want multiple instances of your modeless opened you will need to set the pointer to NULL here (otherwise your pointer value can't be used as a flag to prohibit other instances of your modeless dialog from being opened). PostNcDestroy apparently isn't run until the parent is destroyed. Once the parent/owner is closed then a PostNcDestroy is run for each modeless that opened - one right after another. void CDialogDerived::OnClose() { CDialog::OnClose(); m_pParent->m_pModelessDialog = NULL; } void CDialogDerived::PostNcDestroy() { CDialog::PostNcDestroy(); m_pParent->m_pModelessDialog = NULL; //in case destroy window is called from your code and OnClose is never run delete this; } Example 2. This seems like a cleaner approach as the Dialog is destroyed immediately and thus your pointer is set to NULL immediately void CDialogDerived::OnClose() { CDialog::OnClose(); DestroyWindow(); } void CDialogDerived::PostNcDestroy() { CDialog::PostNcDestroy(); m_pParent->m_pModelessDialog = NULL; delete this; } Any thoughts pro and con to either of these approaches? I'm curious why the PostNcDestroy is not run promptly with the OnClose call on the first example. Thanks!
mx483 wrote:
you don't want multiple instances of your modeless opened you will need to set the pointer to NULL
This is a curious way to avoid multiple instance. Is this a child dialog of your app or, the app itself ? Why don't you check if the dialog already exists before starting a new one ? If you destroy your dialog, a simple check on SafeWindow will tell you that your pointer is not used anymore. YOu could even fire up a message to your parent to reset the pointer if you really want to. As for the examples, I think this is sufficient, no need to intercept another message.
void CDialogDerived::PostNcDestroy()
{
m_pParent->m_pModelessDialog = NULL;
delete this;
CDialog::PostNcDestroy(); // I usually call the base class AFTER my processing}
~RaGE(); [edit] I wrote teh same as Shog because I forgot to post my message right away... Nevermind, and great to see we are on the same wave length [/edit] -- modified at 10:01 Wednesday 15th February, 2006
-
mx483 wrote:
you don't want multiple instances of your modeless opened you will need to set the pointer to NULL
This is a curious way to avoid multiple instance. Is this a child dialog of your app or, the app itself ? Why don't you check if the dialog already exists before starting a new one ? If you destroy your dialog, a simple check on SafeWindow will tell you that your pointer is not used anymore. YOu could even fire up a message to your parent to reset the pointer if you really want to. As for the examples, I think this is sufficient, no need to intercept another message.
void CDialogDerived::PostNcDestroy()
{
m_pParent->m_pModelessDialog = NULL;
delete this;
CDialog::PostNcDestroy(); // I usually call the base class AFTER my processing}
~RaGE(); [edit] I wrote teh same as Shog because I forgot to post my message right away... Nevermind, and great to see we are on the same wave length [/edit] -- modified at 10:01 Wednesday 15th February, 2006
Thanks for the responses. I do check for the NULL pointer before opening an instance. I didn't include that code because it isn't relevant. What is relevant though is the correct way to handle the WM_CLOSE message. And my curiosity was I didn't understand why the WM_CLOSE message didn't run the Destroy Window right away - only after parent was closed.
-
mx483 wrote:
you don't want multiple instances of your modeless opened you will need to set the pointer to NULL
This is a curious way to avoid multiple instance. Is this a child dialog of your app or, the app itself ? Why don't you check if the dialog already exists before starting a new one ? If you destroy your dialog, a simple check on SafeWindow will tell you that your pointer is not used anymore. YOu could even fire up a message to your parent to reset the pointer if you really want to. As for the examples, I think this is sufficient, no need to intercept another message.
void CDialogDerived::PostNcDestroy()
{
m_pParent->m_pModelessDialog = NULL;
delete this;
CDialog::PostNcDestroy(); // I usually call the base class AFTER my processing}
~RaGE(); [edit] I wrote teh same as Shog because I forgot to post my message right away... Nevermind, and great to see we are on the same wave length [/edit] -- modified at 10:01 Wednesday 15th February, 2006
I think you both missed my message. You have to intercept the WM_CLOSE message. If you don't you are 1. Not Destroying your window till your parent is closed. 2. Therefore you PostNcDestroy is not run until your parent is closed 3. Therefore, how does your app know that the pointer is no longer valid. You can run GetSafeWnd all day long - it still points to valid memory...cause you never bothered to delete the CWnd object...until the parent is closed. So back to my original question please.
-
I think you both missed my message. You have to intercept the WM_CLOSE message. If you don't you are 1. Not Destroying your window till your parent is closed. 2. Therefore you PostNcDestroy is not run until your parent is closed 3. Therefore, how does your app know that the pointer is no longer valid. You can run GetSafeWnd all day long - it still points to valid memory...cause you never bothered to delete the CWnd object...until the parent is closed. So back to my original question please.
mx483 wrote:
1. Not Destroying your window till your parent is closed.
Sorry, i guess i missed that. Sounds like you're missing a few things in your implementation, so i'll go over the steps to doing a proper modeless dialog in MFC:
- Create with
Create()
, followed byShowWindow()
, notDoModal()
(i'm sure you're already doing this :) ) - Never call
EndModalLoop()
(orEndDialog()
) - Never let the default behavior run when it will end up doing what you avoided in #1 or #2
- No default behavior for
OnOK()
orOnCancel()
(WM_COMMAND
handlers forIDOK
andIDCANCEL
) - Be aware that the default behavior for
WM_CLOSE
just posts aWM_COMMAND
forIDCANCEL
(so it'll end up callingIDCANCEL
) - Be aware that various keys can trigger
OnCancel()
/OnOK()
directly (ENTER, ESC) or indirectly (Alt+F4 ->WM_CLOSE
->OnCancel()
). So you really do need to override these commands, even if you don't have an explicitIDOK
orIDCANCEL
button on your dialog.
So the likely explanation for the behavior you describe is that you're letting the baseclass call
EndModalLoop()
, which just hides the window. Then when the parent is destroyed, its (now hidden) children are also destroyed, causing the wave of delayed destruction you noticed. So your fix is simple: overrideOnOK()
andOnCancel()
and cause them to callDestroyWindow()
rather than the baseclass implementation. Optionally, do the same forOnClose()
(isn't necessary, but will make it more obvious what you're doing). And i'd still encourage you to avoid messing with data in the parent window class (and especially doingdelete this
). Nothing absolutely wrong with it, but it's messy - let the owner handle that stuff.---- Scripts i've known... CPhog 0.9.9 - make CP better. Forum Bookmark 0.2.5 - bookmark forum posts on Pensieve
- Create with
-
mx483 wrote:
1. Not Destroying your window till your parent is closed.
Sorry, i guess i missed that. Sounds like you're missing a few things in your implementation, so i'll go over the steps to doing a proper modeless dialog in MFC:
- Create with
Create()
, followed byShowWindow()
, notDoModal()
(i'm sure you're already doing this :) ) - Never call
EndModalLoop()
(orEndDialog()
) - Never let the default behavior run when it will end up doing what you avoided in #1 or #2
- No default behavior for
OnOK()
orOnCancel()
(WM_COMMAND
handlers forIDOK
andIDCANCEL
) - Be aware that the default behavior for
WM_CLOSE
just posts aWM_COMMAND
forIDCANCEL
(so it'll end up callingIDCANCEL
) - Be aware that various keys can trigger
OnCancel()
/OnOK()
directly (ENTER, ESC) or indirectly (Alt+F4 ->WM_CLOSE
->OnCancel()
). So you really do need to override these commands, even if you don't have an explicitIDOK
orIDCANCEL
button on your dialog.
So the likely explanation for the behavior you describe is that you're letting the baseclass call
EndModalLoop()
, which just hides the window. Then when the parent is destroyed, its (now hidden) children are also destroyed, causing the wave of delayed destruction you noticed. So your fix is simple: overrideOnOK()
andOnCancel()
and cause them to callDestroyWindow()
rather than the baseclass implementation. Optionally, do the same forOnClose()
(isn't necessary, but will make it more obvious what you're doing). And i'd still encourage you to avoid messing with data in the parent window class (and especially doingdelete this
). Nothing absolutely wrong with it, but it's messy - let the owner handle that stuff.---- Scripts i've known... CPhog 0.9.9 - make CP better. Forum Bookmark 0.2.5 - bookmark forum posts on Pensieve
Bingo! Hey thanks, Here was where I wasn't understanding: Be aware that the default behavior for WM_CLOSE just posts a WM_COMMAND for IDCANCEL (so it'll end up calling IDCANCEL). Thanks for taking the time to write again and explaining why the window wasn't really destroyed when the WM_CLOSE was called. I'll also strongly consider your other suggestions. :)
- Create with
-
Need your thoughts and/or opinion. Question at the bottom. When user closes the modeless dialog box you can trap the WM_CLOSE message to insure proper cleanup. According to Help CWnd::OnClose The framework calls this member function as a signal that the CWnd or an application is to terminate. The default implementation calls DestroyWindow. I found two ways to approach the user who closes the modeless dialog box by clicking the x on the upper right hand corner of the window Looking at the first example below. I found out that if you don't call DestroyWindow() from your OnClose() and you don't want multiple instances of your modeless opened you will need to set the pointer to NULL here (otherwise your pointer value can't be used as a flag to prohibit other instances of your modeless dialog from being opened). PostNcDestroy apparently isn't run until the parent is destroyed. Once the parent/owner is closed then a PostNcDestroy is run for each modeless that opened - one right after another. void CDialogDerived::OnClose() { CDialog::OnClose(); m_pParent->m_pModelessDialog = NULL; } void CDialogDerived::PostNcDestroy() { CDialog::PostNcDestroy(); m_pParent->m_pModelessDialog = NULL; //in case destroy window is called from your code and OnClose is never run delete this; } Example 2. This seems like a cleaner approach as the Dialog is destroyed immediately and thus your pointer is set to NULL immediately void CDialogDerived::OnClose() { CDialog::OnClose(); DestroyWindow(); } void CDialogDerived::PostNcDestroy() { CDialog::PostNcDestroy(); m_pParent->m_pModelessDialog = NULL; delete this; } Any thoughts pro and con to either of these approaches? I'm curious why the PostNcDestroy is not run promptly with the OnClose call on the first example. Thanks!
I believe that fact that the Dialog was not getting destroyed was because the WM_CLOSE message is being overridden by CDialog and calling and IDCANCEL. That would make sense. Before I posted the question though I attempted to follow the WM_CLOSE message to see where it was going - hence the reference to CWnd::Close from Help at the top of the question. When I put a break point at the OnClose function -user clicks the x at upper right hand part of dialog(Created through ClassWizard capturing the WM_CLOSE message) I am lead to this. _AFXWIN_INLINE void CWnd::OnClose() { Default(); } Continuing I am lead to LRESULT CWnd::DefWindowProc(UINT nMsg, WPARAM wParam, LPARAM lParam) Message being passed is 16 and IDCANCEL is defined as 2 Now I'm scratching my head. Wouldn't I see an IDCANCEL call or something like that. CWnd is supposed to destroy the window Thanks
-
I believe that fact that the Dialog was not getting destroyed was because the WM_CLOSE message is being overridden by CDialog and calling and IDCANCEL. That would make sense. Before I posted the question though I attempted to follow the WM_CLOSE message to see where it was going - hence the reference to CWnd::Close from Help at the top of the question. When I put a break point at the OnClose function -user clicks the x at upper right hand part of dialog(Created through ClassWizard capturing the WM_CLOSE message) I am lead to this. _AFXWIN_INLINE void CWnd::OnClose() { Default(); } Continuing I am lead to LRESULT CWnd::DefWindowProc(UINT nMsg, WPARAM wParam, LPARAM lParam) Message being passed is 16 and IDCANCEL is defined as 2 Now I'm scratching my head. Wouldn't I see an IDCANCEL call or something like that. CWnd is supposed to destroy the window Thanks
(btw - no harm in starting a new thread with offshoot topics like this :) ) This question takes you into the fun and exciting world of Classic Win32. Well, perhaps i'm being generous with "fun and exciting"... but anyway... In a nutshell, this is how it works: in basic ol' Win32 (and Win16 for that matter), dialogs are not quite the same as "regular" windows. They aren't created in the same way, they don't get passed messages in the same way, and the default behavior for unprocessed messages isn't the same either. MFC does a lot to hide this from you, but at its core it still behaves in much the same way. Which makes things interesting when you're creating a non-modal by deriving a class from
CDialog
, because you inherit a lot of defaults that you really don't want. And one of those behaviors is what happens when you call::DefWindowProc()
withWM_CLOSE
as the message. As you've found, dialogs do not callDestroyWindow()
- they postWM_COMMAND
withWPARAM
set to 2 (IDCANCEL
). And because the message is posted, you don't get a nice call stack leading back toOnClose()
fromOnCancel()
. Why is this so? Because you don't close dialogs - real modal dialogs - by callingDestroyWindow()
. They have their own message loops that need to be cleaned up, and those take care of actually destroying the window, so you callEndModalLoop()
in MFC, orEndDialog()
in Win32. And that is what the default behavior forOnClose()
/OnOk()
end up doing... which as you've noticed, is nearly useless for non-modal dialogs.---- Scripts i've known... CPhog 0.9.9 - make CP better. Forum Bookmark 0.2.5 - bookmark forum posts on Pensieve Print forum 0.1.1 - printer-friendly forums
-
(btw - no harm in starting a new thread with offshoot topics like this :) ) This question takes you into the fun and exciting world of Classic Win32. Well, perhaps i'm being generous with "fun and exciting"... but anyway... In a nutshell, this is how it works: in basic ol' Win32 (and Win16 for that matter), dialogs are not quite the same as "regular" windows. They aren't created in the same way, they don't get passed messages in the same way, and the default behavior for unprocessed messages isn't the same either. MFC does a lot to hide this from you, but at its core it still behaves in much the same way. Which makes things interesting when you're creating a non-modal by deriving a class from
CDialog
, because you inherit a lot of defaults that you really don't want. And one of those behaviors is what happens when you call::DefWindowProc()
withWM_CLOSE
as the message. As you've found, dialogs do not callDestroyWindow()
- they postWM_COMMAND
withWPARAM
set to 2 (IDCANCEL
). And because the message is posted, you don't get a nice call stack leading back toOnClose()
fromOnCancel()
. Why is this so? Because you don't close dialogs - real modal dialogs - by callingDestroyWindow()
. They have their own message loops that need to be cleaned up, and those take care of actually destroying the window, so you callEndModalLoop()
in MFC, orEndDialog()
in Win32. And that is what the default behavior forOnClose()
/OnOk()
end up doing... which as you've noticed, is nearly useless for non-modal dialogs.---- Scripts i've known... CPhog 0.9.9 - make CP better. Forum Bookmark 0.2.5 - bookmark forum posts on Pensieve Print forum 0.1.1 - printer-friendly forums
Yeah, its starting to make sense. But why when I hovered over the nMsg in the DefWindowProc did the message come back as 16. I know IDCANCEL is 2 #define IDOK 1 #define IDCANCEL 2 #define IDABORT 3 #define IDRETRY 4 #define IDIGNORE 5 #define IDYES 6 #define IDNO 7 #if(WINVER >= 0x0400) #define IDCLOSE 8 #define IDHELP 9 Still scratching. Oh and by the way thanks for your help.
-
Yeah, its starting to make sense. But why when I hovered over the nMsg in the DefWindowProc did the message come back as 16. I know IDCANCEL is 2 #define IDOK 1 #define IDCANCEL 2 #define IDABORT 3 #define IDRETRY 4 #define IDIGNORE 5 #define IDYES 6 #define IDNO 7 #if(WINVER >= 0x0400) #define IDCLOSE 8 #define IDHELP 9 Still scratching. Oh and by the way thanks for your help.
Here's a little trick: in the debugger, put a number (or variable) in the watch window, and follow the name with
, wm
- it'll display the name of the window message that the number maps to. But you're not gonna seeIDCANCEL
being passed innMsg
anyway - it's passed in thewparam
field, while the message isWM_COMMAND
.---- Scripts i've known... CPhog 0.9.9 - make CP better. Forum Bookmark 0.2.5 - bookmark forum posts on Pensieve Print forum 0.1.1 - printer-friendly forums
-
Here's a little trick: in the debugger, put a number (or variable) in the watch window, and follow the name with
, wm
- it'll display the name of the window message that the number maps to. But you're not gonna seeIDCANCEL
being passed innMsg
anyway - it's passed in thewparam
field, while the message isWM_COMMAND
.---- Scripts i've known... CPhog 0.9.9 - make CP better. Forum Bookmark 0.2.5 - bookmark forum posts on Pensieve Print forum 0.1.1 - printer-friendly forums
Sounds neat, I wish I could get it to work. Both wparam and lparam are zero with nMsg = 16. I using Visual C++ 6.0 so maybe the watch stuff is a new edition. IDCANCEL, wm gives me an error message in the watch window. I also looked up the value of WM_COMMAND and it is 273
-
Sounds neat, I wish I could get it to work. Both wparam and lparam are zero with nMsg = 16. I using Visual C++ 6.0 so maybe the watch stuff is a new edition. IDCANCEL, wm gives me an error message in the watch window. I also looked up the value of WM_COMMAND and it is 273
mx483 wrote:
IDCANCEL, wm gives me an error message in the watch window.
Wrong way about. Try:
16, wm
ornMsg, wm
Put a breakpoint inOnCancel()
, and look up the callstack when it gets hit - you'll seeWM_COMMAND
being processed then.---- Scripts i've known... CPhog 0.9.9 - make CP better. Forum Bookmark 0.2.5 - bookmark forum posts on Pensieve Print forum 0.1.1 - printer-friendly forums
-
mx483 wrote:
IDCANCEL, wm gives me an error message in the watch window.
Wrong way about. Try:
16, wm
ornMsg, wm
Put a breakpoint inOnCancel()
, and look up the callstack when it gets hit - you'll seeWM_COMMAND
being processed then.---- Scripts i've known... CPhog 0.9.9 - make CP better. Forum Bookmark 0.2.5 - bookmark forum posts on Pensieve Print forum 0.1.1 - printer-friendly forums
Thanks for being so patient. Yep It works, it was case sensitive. I see where the WM_CLOSE message is being posted. I can keep pressing F11 and trace into the functions where finally the DefWindowProc shows up. Then I can go to the call stack and see all this too. I think I'm back to wondering why (according to you) I should be seeing a WM_COMMAND message instead of a WM_CLOSE message. Hey thanks you are teaching me a bunch!
-
Thanks for being so patient. Yep It works, it was case sensitive. I see where the WM_CLOSE message is being posted. I can keep pressing F11 and trace into the functions where finally the DefWindowProc shows up. Then I can go to the call stack and see all this too. I think I'm back to wondering why (according to you) I should be seeing a WM_COMMAND message instead of a WM_CLOSE message. Hey thanks you are teaching me a bunch!
mx483 wrote:
I think I'm back to wondering why (according to you) I should be seeing a WM_COMMAND message instead of a WM_CLOSE message.
You'll see both -
WM_CLOSE
first, then (if you let the default handler handle it)WM_COMMAND
.---- Scripts i've known... CPhog 0.9.9 - make CP better. Forum Bookmark 0.2.5 - bookmark forum posts on Pensieve Print forum 0.1.1 - printer-friendly forums
-
mx483 wrote:
I think I'm back to wondering why (according to you) I should be seeing a WM_COMMAND message instead of a WM_CLOSE message.
You'll see both -
WM_CLOSE
first, then (if you let the default handler handle it)WM_COMMAND
.---- Scripts i've known... CPhog 0.9.9 - make CP better. Forum Bookmark 0.2.5 - bookmark forum posts on Pensieve Print forum 0.1.1 - printer-friendly forums