Modal CDIALOG causes assertion failure on second call
-
jeudi 23 août 2012 14:59
Hello again,
I have created a CDialog on the stack.
Then I issue a DoModal call. When the dialog box processing completes, I capture a return value.
Sometimes, I want to issue the DoModal a second time, on the same stack item, however this causes an assertion.
MYDIALOG workDialog:
iRetrunCode = workDialog.DoModal();
.....check for iReturnCode and issue another workDialog.DoModal().....results in assertion.
I can code around this but I want to know if there is something I am missing here.
Is there a simple call I can invoke to "reset" the dialog environment to prevent the assert?
Thanks
Cameron Conacher
Toutes les réponses
-
jeudi 23 août 2012 16:15Is your dialog the mainwindow and the application a dialog based application?
-
jeudi 23 août 2012 16:27
No it is not.
I was just reading through tech notes (TN017), and if I am understanding things correctly, I just need to do a DestroyWindow after the first DoModal.
http://msdn.microsoft.com/en-us/library/5zba4hah(v=vs.71)
Cameron Conacher
-
jeudi 23 août 2012 16:54
Tha may work, but it depends on your CDialog derived class. Your constructor will only called before the first call of DoModal, just when the variable is created. If you do important inits in your constructor you may have problems or not the expected results.
But at all it is a kind of strange to me closing dialog and than immediately open it again. A better way would prevent the close of the dialog as long as the user did not finished what das to be finished, and if everything is right the dialog may be closed. This is easy to create, just overwrite OnOk and OnCancel-
-
jeudi 23 août 2012 17:59
Hello Bordon,
I aggree with you.
I have a corner case here where I have created this CDialog derived utility class, specifically so that I can dynamically change the fonts and resize the dialog.
I override OnDialog, and then use the CDialogTemplate to change the default font.
Which all works terrifically.
Howewver, when I select the menu option to change the font, the change is accepted,, and the "polite" way I found to display the dialog with updated fonts/resized was to simply restart the dialog. Another choice was to display a message to the User to tell them to restart the dialog for the changes to take effect.
Which may not be the best way either, but it got me here.
Cameron Conacher
-
jeudi 23 août 2012 19:10
Calling DestroyWindow is absolutely unnecessary if you rely on default implementation of the dialog termination. Dialog takes care of it from EndDialog. Where did you find this document? Link you posted does not work (page not found).
I just tested this behavior using dialog created by default, without any changes to the code in the derived class created by the wizard.
I have invoked DoModal 10 times in the row without any problems. Of course, since dialog object is already instantiated, constructor is not called and destructor is called after dialog object goes out of scope.
Conclusion?
Do not look for any explanation of the behavior outside of your implementation; your code causes the assertion.
Can you tell where assertion takes place? Can you also post code snippet or the location and file? Usually there is a comment why assertion fails.
JohnCz Please consider voting if you find this post helpful.
-
jeudi 23 août 2012 20:14
Hello John,
Here is a snippet from the assertion:
BOOL CDialog::InitModalIndirect(HGLOBAL hDialogTemplate, CWnd* pParentWnd)
{
// must be called on an empty constructed CDialog
ASSERT(m_lpszTemplateName == NULL);
ASSERT(m_hDialogTemplate == NULL); ------> Asserts Here.
ASSERT(hDialogTemplate != NULL);
I am arriving here from my code where I have overridden the DoModal();
/*
Get the address of the modified dialog template.
*/
LPSTR pData = (LPSTR)GlobalLock(dlt.m_hTemplate);
/*
Let the framework know we want to use this new dialog template.
*/
m_lpszTemplateName = NULL;
InitModalIndirect(pData);
/*
Load up the Popup Help Text File for the Dialog.
*/
m_szPopupHelpTextFileNameString.LoadString(m_uiPopupHelpTextFileNameString);
/*
Display the Dialog.
*/
nResult = CDialog::DoModal();
/*
Unlock the memory object we created.
*/
GlobalUnlock(dlt.m_hTemplate);
Apparently m_hDialogTemplate is not NULL.
I did try with the DestroyWindow and saw the same result.Thanks
Cameron Conacher
-
jeudi 23 août 2012 21:37
I was under impression that you are using OCC manager to manage template changes.
If I understand correctly, you are calling InitModalIndirect from you overridden DoModal.
This is not correct approach.
InitModalIndirect should be called before Do modal, only if you are creating dialog from the template loaded to memory before calling DoModal after empty dialog constructor is called to instantiate dialog object.
Once this is done you cannot call InitModalIndirect. That is why it asserts, since dialog template is already loaded form initial instantiation of an object.
I would suggest changing approach and use Occ manager.
JohnCz Please consider voting if you find this post helpful.
- Marqué comme réponse Cameron_C jeudi 23 août 2012 22:00
-
jeudi 23 août 2012 21:59
I must say I am a bit saddened, but thanks for pointing me in the proper direction John.
Cameron Conacher
-
jeudi 23 août 2012 22:08
You are very welcome, glad to be of help.
Let me knwo if you need any help with OCC manager.
JohnCz Please consider voting if you find this post helpful.
-
vendredi 24 août 2012 13:40
Hello John,
Just a quick update.
The link provided above is fine but for some reason best known to others, the the closing parenthesis of the link is not included withing the hyperlinked text.
If you cut and paste the link to I.E. you will go to the Tech Note.
Cameron Conacher
-
lundi 27 août 2012 19:33
I am not sure why you are referring to http://msdn.microsoft.com/en-us/library/5zba4hah(v=vs.71)
This article describes how to utilize DestroyWindow in order to maintain memory allocations for certain windows.
This method is used by vew/frame/document and some other classes. Those classes use forced heap allocation and in order to clean up allocated memoru, they call DestroyWindow. MFC framework calls PostNcDestroy where object can perform seppuku by calling delete this, knowing that window has already been detached for MFC object..
Dialog in not designed this way and you do not need to call DestroyWindow explicitly, since modal dialog does it for you.Why did you bring up this article?
JohnCz Please consider voting if you find this post helpful.
-
mardi 28 août 2012 16:22
Hello John, Maybe I misunderstand the tech note.
It says :
The following classes are not designed for auto-cleanup. They are normally embedded in other C++ object or on the stack:
- All standard Windows controls (CStatic, CEdit, CListBox, and so on).
- Any child windows derived directly from CWnd (for example, custom controls).
- Splitter windows (CSplitterWnd).
- Default control bars (classes derived from CControlBar, see Technical Note 31 for enabling auto-delete for control bar objects).
- Dialogs (CDialog) designed for modal dialogs on the stack frame.
- All the standard dialogs except CFindReplaceDialog.
- The default dialogs created by ClassWizard.
As I read this, I understood that my Dialogs which I have created on the stack, are not designed for auto cleanup.
That's why I brought up the article. I felt that if I could do a DestroyWindow, I would be able to do the Dialog.DoModal again.
Maybe I am mistaken?
Thanks
Cameron Conacher
-
mardi 28 août 2012 18:57
OK, let’s go over it again:
You instantiate dialog object on the frame and invoke DoModal which in turn creates dialog window and attaches it to the MFC CDialog derived object.
When you dismiss dialog by using OK Cancel buttons or other means, dialog exits own modak loop and calls EndDialog with appropriate exit code.
End dialog, calls DestroyWindow and at this moment dialog object is detached from dialog window.
Dialog object did not go out of scope therefore dialog is still alive with all data that was initialized (including loading template) and data that was collected and saved, in class member variables.
Now you can call DoModal again; there is nothing that can prevent dialog, from creating another dialog window and attach it to the dialog object providing that you do not change data that was initialized when dialog constructor was called the very first time.
Try it and see what happens.
Once you try to change initial data (dialog template that is already loaded) the code asserts, since this is illegal.
That is why I have proposed using OCC manager instead trying to reload the template. This allows changes to the existing dialog template before dialog is created.
I think this is the easiest approach to achieve your goal.
OCC manager is not very well known so, if you need any additional info, please let me know.JohnCz Please consider voting if you find this post helpful.
-
mercredi 29 août 2012 11:51
Hello John,
Everything I know about OCC Manager has been from you :-)
Can you point me to some appropriate information?
Thanks
Cameron Conacher
-
mardi 4 septembre 2012 17:39
There is very little (next to nothing) documented MFC class: COccManager.
Documentation states that this class manages all MFC control containers for a process. It is used to control OLE interfaces (ActiveX controls) creation and activation. It also handles pointers to other OLE interfaces but this is outside of this topic.
This is done for all control containers that have OCC (Ole Control Container) support enabled. All MFC applications are created with OCC support enabled by default.
Without going into more details, COccManger allows you to do much more. Wizard creates MFC applications with default OCC manager. You can however use your own manager, following steps below.
Derive your own class derived from COccManager.
Override virtual PreCreateDialog member of the class.//Header declaration: virtual const DLGTEMPLATE* PreCreateDialog(_AFX_OCC_DIALOG_INFO* pOccDialogInfo, const DLGTEMPLATE* pOrigTemplate); //The implementation: const DLGTEMPLATE* CDlgOccManager::PreCreateDialog(_AFX_OCC_DIALOG_INFO* pOccDialogInfo, const DLGTEMPLATE* pOrigTemplate) { return COccManager::PreCreateDialog(pOccDialogInfo, pOrigTemplate); }Now, notice in the dialog’s code, that CreateDlgIndirect calls PreCreateDialog member of the COccManager, using pointer assigned before this call:
COccManager* pOccManager = afxOccManager;
We are almost done here. afxOccManager is a global pointer initialized by the call from the InitInstance of the application class: AfxEnableControlContainer. This function takes NULL by default but we can use it to hook up our object.
In your app, declare an object of the derived class type (similar way theApp is declared). In the app’s InitInstance pass pointer to this object:
AfxEnableControlContainer(&theOccManager);
This will assign your pointer to the afxOccManager global used by the dialog class to call manager’s PreCreateDialog.
Now you can do any changes to the dialog template before any control is created.
Below is an example of simple font change:const DLGTEMPLATE* CDlgOccManager::ChangeDilogFont(_AFX_OCC_DIALOG_INFO* pOccDialogInfo, const DLGTEMPLATE* pOrigTemplate) { CDialogTemplate ModifiedTemplate(pOrigTemplate); ModifiedTemplate.SetFont(_T("Times New Roman"), 14); return COccManager::PreCreateDialog(pOccDialogInfo, (DLGTEMPLATE*)ModifiedTemplate.Detach()); }Of course, this is simplification just to demonstrate insights. I have written application trying to meet your requirements. You can find it here.
JohnCz Please consider voting if you find this post helpful.
- Marqué comme réponse Cameron_C jeudi 6 septembre 2012 12:39
-
jeudi 6 septembre 2012 12:44
John,
I worked thorugh what you have provided.
A very very elegant solution.
I had absolutely no idea that this could be done this way.
Makes me wonder what else I might have been applying a sledgehammer to make fit.
Now I have to ask (I don't expect you to know of course), but why the heck is COccManager not documented?
And, how did you discover it to begin with?
Cameron Conacher
-
jeudi 6 septembre 2012 15:43
Now I have to ask (I don't expect you to know of course), but why the heck is COccManager not documented?
You guessed right. I do not know. COccManager class is not the only one topic that is not documented. The new MFC documentation is another example of a very poor documentation.
I guess, this is for the following reason: it was made public but is not documented, hence we do not support it.
Meaning: do not ask us (MS) to help you.
And, how did you discover it to begin with?
I am programming for Windows for ages now (since Windows 2 and 3.1.
I believe COccManager class did not exist in Visual C++ included in Visual Workbench 2.0; it was introduced with MFC included with VS 4.2 called Microsoft Developer Studio and I think it was included in MSDN subscribtion.
The class was there but the line allowing setting AfxEnableControlContainer was not added to the code generated by the App Wizard. In Visual Studio 6.0, it was.
The MFC code was still very limited and the only way to learn was to explore the code. That is how I found COccManager and ways to use it.
JohnCz Please consider voting if you find this post helpful.

