One of our MFC legacy applications shows a behavior I do not really understand right now. I am not an MFC expert, however. The symptom that I observe and which is causing problems is that OnInitialUpdate of the views is called twice by the framework during app start-up. As I understand the documentation it should be called only once.
My investigation revealed that the app calls the following code in its InitInstance:
CMainFrame *const pMainFrame = new CMainFrame;
if (!pMainFrame->LoadFrame(IDR_MAINFRAME)) { return FALSE; }
m_pMainWnd = pMainFrame;
This causes CFrameWnd::LoadFrame being called inside the framework. From here both calls to OnInitialUpdate originate. The first when Create is called and the second when SendMessageToDescendants is called to send WM_INITIALUPDATE.
Our app overloads CMainFrame::OnCreateClient to create a splitter window:
Splitter.CreateStatic(this, 2, 2);
Splitter.CreateView(0, 0, RUNTIME_CLASS(MyView),CSize(0,0), pContext);
// ...
The call to Splitter.CreateView is what triggers OnInitialUpdate to be called the first time. But as I could see from the documentation this implementation is as intended. So unfortunately this is where I get stuck. I don't understand if the OnInitialUpdate call from SendMessageToDescendants(WM_INITIALUPDATE, 0, 0, TRUE, TRUE) or from CSplitterWnd::CreateView is wrong.
What else could I take a look at to figure out the root cause of this problem?
Is it possible for OnInitialUpdate to be called twice and we need to make sure that our app can cope with this?
If the answer to question 2 is yes: What is the best way to archive this?
EDIT
Further investigation has shown that CSplitterWnd::CreateView only sends WM_INITIALUPDATE if the passed CCreateContext is a nullptr. So I checked CMainFrame::OnCreateClient and it is indeed called by the framework with pContext being nullptr.
EDIT2
As requested in the comments here is the code adding the DocTemplate:
CSingleDocTemplate *const pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CMenuDoc),
RUNTIME_CLASS(CMainFrame),
RUNTIME_CLASS(CMenuView));
if (!pDocTemplate)
return FALSE;
AddDocTemplate(pDocTemplate);
There is also a call
Splitter.CreateView(1, 1, RUNTIME_CLASS(CMenuView),CSize(0,0), pContext);
in the CMainFrame::OnCreateClient. If I understand the comments correctly this is wrong?
The two calls of OnInitialUpdate are for the same instances of the views. If I followed the stack trace correctly the reason for the CCreateContext being a nullptr is that no context is passed to LoadFrame. The context passed to CFrameWnd::LoadFrame is just forwarded to CMainFrame::OnCreateClient. But what context should I pass?
Related
As per this stackoverflow question:
What is the correct way to programmatically quit an MFC application?
I am using AfxGetMainWnd()->PostMessage(WM_CLOSE,0,0); to exit an MFC program. (SDI, CFrameWnd containing a CSplitterWnd with two CFormViews)
As expected, this calls DestroyWindow().
The problem I am facing is that after the derived CFormView destruction, as per MSDN:
After calling DestroyWindow on a non-auto-cleanup object, the C++ object will still be around, but m_hWnd will be NULL. [MSDN]
Now the CView destructor is called and at the point it does the
CDocument::RemoveView()...
CDocument::UpdateFrameCounts()
it fails on the following assert: ASSERT(::IsWindow(pView->m_hWnd));
I checked and the m_hWnd is already set to NULL in the derived CView destructor called just before.
What am I doing wrong ?
EDIT:
Here is a chart illustrating why I want to send a WM_CLOSE message and not a WM_QUIT.
I think the answer lays in this MSDN Technical Note, but I can't figure it out.
EDIT 2:
The order that things get called:
1- AfxGetMainWnd()->PostMessage(WM_CLOSE,0,0);
2- Derived CFrameWnd::OnClose()
3- CFrameWnd::OnClose()
which calls CWinApp::CloseAllDocuments(BOOL bEndSession);
which calls CDocManager::CloseAllDocuments(BOOL bEndSession)
which calls CDocTemplate::CloseAllDocuments(BOOL)
which calls CDocument::OnCloseDocument()
Now, in this function
while (!m_viewList.IsEmpty())
{
// get frame attached to the view
CView* pView = (CView*)m_viewList.GetHead();
ASSERT_VALID(pView);
CFrameWnd* pFrame = pView->EnsureParentFrame();
// and close it
PreCloseFrame(pFrame);
pFrame->DestroyWindow();
// will destroy the view as well
}
So we see that CWnd::DestroyWindow() is called, so:
4- Derived CFormView destructor
5- CScrollView::~CScrollView()
6- CView::~CView()
which calls CDocument::RemoveView(CView* pView)
which calls CDocument::OnChangedViewList()
which calls CDocument::UpdateFrameCounts()
Which crashes here: ASSERT(::IsWindow(pView->m_hWnd));
because pView->m_hWnd is NULL...
EDIT 3:
I figured out what the problem was:
The destructor of the first view was deleting an uninitialized pointer, which is UB. This was making the destructor hang and never complete.
Usually, the destructor of the second view is only called upon completion of the first one. But in this case it was still being executed although the first one never completed.
Since the first view base class destructors were never called, this function was never called for the first view:
void CDocument::RemoveView(CView* pView)
{
ASSERT_VALID(pView);
ASSERT(pView->m_pDocument == this); // must be attached to us
m_viewList.RemoveAt(m_viewList.Find(pView));
pView->m_pDocument = NULL;
OnChangedViewList(); // must be the last thing done to the document
}
Where we can see that the view is removed from the m_viewList.
This means that when the second view destructor completes, in:
void CDocument::UpdateFrameCounts()
// assumes 1 doc per frame
{
// walk all frames of views (mark and sweep approach)
POSITION pos = GetFirstViewPosition();
while (pos != NULL)
{
...
The pos is supposed to be NULL, but it is not. Which lead to the crash.
I think the way you are closing the frame is not the issue there.
My guess is that you destroy one of the views by hand whereas you should let MFC delete them (you probably called DestroyWindow on one of them)
Call ::PostQuitMessage(0); to close the app.
The problem was resolved, see EDIT 3 in the question for the solution.
Good evening,
I am stuck on a really strange problem.
I have a CSplitterWnd object and I want to add two child pane to it.
The two pane are from two subclass of the CFormView class.
I add them with the following code :
(Please note that this code is called in the OnCreateClient function of the CMainFrame class)
BOOL result = SplitWindow.CreateStatic(this, 1, 2);
if (!result)
return FALSE;
CRect cRect;
GetClientRect(&cRect);
int iHeight = theApp.GetProfile().ImageHeight ? theApp.GetProfile().ImageWidth : cRect.Height() / 2;
CSize szSize1(cRect.Width() / 2, iHeight);
if (!SplitWindow.CreateView(0, 0, RUNTIME_CLASS(CView1), szSize1, pContext))
{
SplitWindow.DestroyWindow();
return FALSE;
}
CSize szSize2(cRect.Width() / 2, iHeight);
if (!SplitWindow.CreateView(0, 1, RUNTIME_CLASS(CView2), szSize2, pContext))
{
SplitWindow.DestroyWindow();
return FALSE;
}
The CView1 and CView2 class are two class derived from a class CBaseView class which itself is a child of CFormView (I use only one dialog definition in CBaseView).
The problem I am facing is that the call to "CreateView" always returns FALSE.
I went inside the MFC code and found out a strange thing that is shown in the following image :
In the CreateView function of CSplitterWnd class, I see that a check to the pWnd pointer address is failing despite the pointer not being NULL.
I am using debug compilation with all optimizations disabled so what I see in the debugger should be true, right ?
Is there someone that would know about this ?
My main purpose is to be able to create those sub-views.
I have to add that at first it was working, then I added the class CBaseView and derivated the CView1 and CView2 from CBaseView.
When it was working CView1 and CView2 were at that time children of CFormView.
I tried to go back to this configuration, but I saw exactly the same problem as I have now.
Hope someone could help.
Thanks in advance.
I found the solution.
It was due to the resource dialog linked to the CBaseView.
It contains MFCRangeSlider resource that was not yet handled. Deleting this resource or handling it properly is enough to succeed in creating the child views.
I'm having a strange problem with wxWidgets. I have the following code
MyFrame::OnDoSomeLongThing(...) {
progScreen = new wxProgressDialog(text,text,number,this,wxPD_AUTO_HIDE); // wxProgressDialog *progScreen is class member
doPartOfThing() // calls the update method at the end of it
....
doLastPartOfThing() // again calls update method that pushes value to 100/100
progScreen->Destroy();
}
MyFrame::update() {
progScreen->Update(newValue);
}
Now here's the thing. I can literally comment out the lines relating to progScreen, just let the process go without using a progress dialog, after all is said and done, my apps exits gracefully when I close the main window.
However, just the use of the progress dialog is somehow extending the life of the application. I've tried Destroy(), I've tried simply 'delete progScreen', and both, every time: I'll close the main frame, the process keeps running, and at some point exits with some astronomical number. The only thing I could think might be relevant, is that the doPartsOfThings methods may call boost::this_thread::sleep, because it involves waiting and whatnot down in my model class. But this shouldn't have anything to do with my problem. Or maybe it does... EDIT: I do want to emphasize that progScreen->Update() IS being called from the main (GUI) thread.
So I ask, am I using a wxProgressDialog correctly? If not, how should it be used?
Thanks for your help!
EDIT:
Well... it turns out that removing wxPD_AUTO_HIDE fixed the problem. I'm still not quite sure what the problem is, but the dialog even still behaves as before. App closes as expected.
I think that you need to override the wxApp method that closes the application so that it closes the wxProgressDialog object before it quits.
wxApp::OnExit
virtual int OnExit()
Override this member function for any processing which needs to be
done as the application is about to exit. OnExit is called after
destroying all application windows and controls, but before wxWidgets
cleanup. Note that it is not called at all if OnInit failed.
The return value of this function is currently ignored, return the
same value as returned by the base class method if you override it.
You will need something like, assuming progScreen is a public attribute of your frame
int myApp::OnExit()
{
(MyFrame*)(GetTopWindow())->progScreen->Destroy()
return wxApp::OnExit();
}
I've created a very simple one-button MFC dialog app that attempts to utilize a callback function. The app complies and runs just fine, but the callback routine never gets triggered.
What needs to be modified in order to get the callback to trigger properly?
You can download the test.zip file here (the test app is in VS 2003 to ensure more people can try it out): http://tinyurl.com/testfile-zip
The code utilizes an alarm class on CodeProject, and the callback function is suppsed to get triggered every 3 seconds (as determined by the code being passed in).
Thanks!
I've looked at your code and the I believe the Function called from the button is the problem
void CTestDlg::OnBnClickedButton1()
{
CAlarmClock clock;
REPEAT_PARMS rp;
ZeroMemory(&rp, sizeof(REPEAT_PARMS));
rp.bRepeatForever = TRUE;
rp.Type = Repeat_Interval;
rp.ss = 3;
clock.SetRepeatAlarm(0, 0, 0, rp, CallbackRtn);
}
This creates the Alarm clock on the function stack.
This CAlarmclock object is therefore destroyed at the end of the function along with its contents.
For it to be able to exist for long enough to actually do the callback
you need to add it as a member variable of your dialog class for it to exist and callback for as long as the dialog exists.
See the example code on the CAlarmclock codeproject page for how to use this class correctly.
My friend and I have each created parts of a GUI using Qt 4. They both work independently and I am trying to integrate his form with the my main window. As of now this is the code I am using to try and load his form:
//connect buttons and such
connect(exitbtn, SIGNAL(triggered()),this,SLOT(terminated()));
connect(add, SIGNAL(triggered()),this,SLOT(add_rec()));
void MainWindowImpl::add_rec()
{
//form quits as soon as it loads...?
DialogImpl dia;//name of his form
dia.show();
}
I have included his header file. The program compiles but when I hit the trigger his form loads up for maybe half a second and then closes. Does anyone know what I am doing wrong?
You have almost get it right. This is because the RAII of C++. If you allocate the Dialog on stack, it would be destructed as soon as the function return.
Assuming MainWindowImpl inherits publically from QWidget, you're looking for this:
void MainWindowImpl::add_rec()
{
// passing "this" to the constructor makes sure dialog will be cleaned up.
// Note that DialogImpl will need a constructor that takes a
// QObject* parent parameter.
DialogImpl* dialog = new DialogImpl(this);
dialog->show();
}
Look at the Qt documentation for examples of how the constructors should look.
Apparently QT4 only allows one instance of an object at a time, however pointers are another matter. Change both the main.cpp and what ever your main window to look something like this:
DialogImpl *dia=new DialogImpl;
dia->show();