this pointer value changed in MFC SDI application - mfc

Now I have the following MFC SDI application code,this code comes from my view class:
void CNew_demo_appView::OnItemUpdate()
{
// TODO: Add your command handler code here
int i=this->GetListCtrl().GetSelectionMark();//get the selected item no
this->GetDocument()->unpacker.GetInformation(i,(BYTE*)(&(this->GetDocument()->fpga_info)));
UpdateFpgaAttrib updatefpgadlg;
updatefpgadlg.DisplayInfo(this->GetDocument()->fpga_info);
updatefpgadlg.DoModal();
}
void CNew_demo_appView::SetItemFpgaAttrib(int index,FPGA_INFO info)
{
this->GetDocument()->fpga_items[0]=info;
}
As you can see, I got a CDialog Derived class called UpdateFpgaAttrib, I instantialize it in the OnItemUpdate function which is called when a menu command is issued, then DoModal()
Popup the Dialog window, on that dialog, there is a button, when clicked it will call the
SetItemFpgaAttrib function which belongs to the View Class,
((CNew_demo_appView*)this->GetParent())->SetItemFpgaAttrib(0,info);
here is the problem, when this
SetItemFpgaAttrib references some data using this pointer, it always got some Access Violation Error, when I invoke this function in other View class function, it is ok,
void CNew_demo_appView::test()
{
SetItemFpgaAttrib(0,this->GetDocument()->fpga_info)
}
when triggered by the popup dialog button, it cause problem, I set break point on the SetItemFpgaAttrib , I found the this pointer value is normal 0x0041237f thing, but when triggered by the button ,it is always 0x00000001, the the GetDocument call alway cause problem. Why is the this pointer value changed,is that caused by the context or something else? I am using Vs2008 SP1

Problem solved, I just want to put the answer here for somebody else who also got this problem someday. The Problem is the
((CNew_demo_appView*)this->GetParent())->SetItemFpgaAttrib(0,info);
the GetParent() is implemented in CWnd, and it returns a CWnd*, that's the problem, the SetItemFpgaAttrib(0,info) is a function of my CDialog-Derived class CNew_demo_appView, it's not the member of CWnd, so the returned CWnd* pointer can't get the code to that function, if you do that like me, you will access some wrong place and will got Access violated error etc. I Need a function that return the original CNew_demo_appView* pointer value, the one in the m_pParentWnd is the value needed(I figure this out when I step into the CWnd::GetParent function), whilethe default GetParent did this:
return (CWnd*)ptr;
to solve this problem, I just add another function to my CDialog-Derived Class:
CWnd* UpdateFpgaAttrib::GetParentView(void)
{
return this->m_pParentWnd; //just return the parent wnd pointer
}
then call this instead of the default GetParent:
CNew_demo_appView* view=(CNew_demo_appView*)this->GetParentView();
Then everything is ok.
So conclusion: the CWnd* cast in the GetParent changed the value of the pointer.

Related

How to get event when click destroy button before document was closed

I have a problem for my MFC project as follows:
When I click on the destroy button, I want to show a messagebox "asking save file" before document closed.
But I can't.
The message is always shown after the document was closed.
A lot of places that I have placed code.
CloseDocument Function of Document Class
OnDestroy Function of MainFrame Class
Destructor Function of View Class
ExitInstance Function of App Class
But without success.
Can someone show what's wrong?
A WM_CLOSE message is sent to a window when the user wants close it. A custom message handler can decide whether it wants to initiate window destruction from here, or go back (or initiate window destruction after storing information).
In an MFC application this is reflected as the OnClose member function of CWnd or a CWnd-derived classes (such as CFrameWnd). Client code can provide a custom implementation and wire it up through the message map. When it does it should only call into the base class implementation if it wants the program to terminate.
Assuming that your CMainFrame class derives from CFrameWnd you will need to make the following changes:
MainFrm.h (add the following class member)
class CMainFrame : public CFrameWnd
{
// ...
protected:
void OnClose();
// ...
};
MainFrm.cpp (message map)
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
// ...
ON_WM_CLOSE()
END_MESSAGE_MAP()
MainFrm.cpp (implementation)
void CMainFrame::OnClose()
{
if (::AfxMessageBox(_T("Close application?"), MB_YESNO) == IDYES)
{
// Have the base class initiate destruction
CFrameWnd::OnClose();
} else {
// Do nothing
}
}
An attempt to close the application's main window pops up a confirmation dialog. If the user selects "Yes", the program terminates. Otherwise the program continues to execute. In that case the document will remain unchanged.
The simpler approach, a Oneliner as described in MSDN
'By calling this function consistently, you ensure that the framework prompts the user to save changes before closing a document. Typically you should use the default value of TRUE for the bModified parameter. To mark a document as clean (unmodified), call this function with a value of FALSE.'
BOOL CMyDocument::OnNewDocument()
{
if (!CDocument::OnNewDocument())
return FALSE;
// TODO: add reinitialization code here
// (SDI documents will reuse this document)
SetModifiedFlag(TRUE); // Set this somewhere, when you want to safe the document before the document goes off
return TRUE;
}

How to get CDocument for derived CMFCShellTreeCtl on an Outlook Style MFC program?

I am new to MFC and have built an "outlook" style MFC app using the wizard. I've extended the CMFCShellTreetCtrl using CMyShellTreeCtrl and had data member variables and all was working fine. Now I want to move the data over to the CDocument class. Since there is several accesses to the data as each item is clicked or enumerated, I thought I would create a member variable m_pDoc to access the public variables in the CDocument. The problem I'm having, I can't find where to get the CDocument as it appears it's not setup when the trees OnCreate is called. That is in OnCreate
CWnd* pWndMain = AfxGetMainWnd();
ASSERT(pWndMain);
ASSERT(pWndMain->IsKindOf(RUNTIME_CLASS(CFrameWnd)) && !pWndMain->IsKindOf(RUNTIME_CLASS(CMDIFrameWnd))); // Not an SDI app.
m_pDoc = (CMyDoc*) ((CFrameWnd*)pWndMain)->GetActiveDocument();
returns NULL in m_pDoc and if I tried in an AfterCreate() (which is called after CreateOutlookBar) it's too late as m_pDoc is already being used and get a crash.
// Create and setup "Outlook" navigation bar:
if (!CreateOutlookBar(m_wndNavigationBar, ID_VIEW_NAVIGATION, m_wndTree, m_wndCalendar, 250))
{
TRACE0("Failed to create navigation pane\n");
return -1; // fail to create
}
m_wndTree.AfterCreate();
Any Ideas?
TIA!!
I came up with the following but I'm not sure if this is the proper way. Again, I'm new to MFC (I always have used Win32 directly). This works so it's AN answer, but is it THE correct answer?
POSITION docpos = AfxGetApp()->GetFirstDocTemplatePosition();
CDocTemplate *doctemplate = AfxGetApp()->GetNextDocTemplate(docpos);
docpos=doctemplate->GetFirstDocPosition();
m_pDoc = (CMyDoc*)doctemplate->GetNextDoc(docpos);
ASSERT(m_pDoc);
ASSERT(m_pDoc->IsKindOf(RUNTIME_CLASS(CMyDoc)));
TIA!!

MFC CView (CFormView) destruction crash

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.

CMFCButton::SetToolTip(str) not work in OnInitDialog() and

I want my CMFCButton to show tooltip when mouse over.
It doesn't work if I use SetToolTip() method in OnInitDialog
CMFCButton* bt = ((CMFCButton*)GetDlgItem(IDC_MFCBUTTON1));
bt->SetTooltip(_T("tooltip"));
BUT it does work if I put this code in message handle function like another button's click handle.
What I want is that the CMFCButton could show tooltip when the dialog is created, where should I put these code?
========================
By the way, The tooltip text I set in the Property view does not work for most time.
I just derived a class
class CMyButton : public CMFCButton
{
public:
void SetDelayFullTextTooltipSet(bool DelayFullTextTooltipSet)
{
m_bDelayFullTextTooltipSet = DelayFullTextTooltipSet;
}
};
Instead of a CMFCButton variable on the Dialog class I use the button, I now have a CMyButton.
And in the OnInitDialog, after the SetTooltip call, I do
button.SetDelayFullTextTooltipSet(FALSE);
Have you called the base class' OnInitDialog()? The main point is that the control needs to be created before you call SetToolTip() on it. Step into OnInitDialog() with the debugger and see if m_hWnd of the control has a value at the moment you call SetToolTip().

Migrated MFC app from VC6 to VS2010, now OnInitDialog() not called for CPropertyPage subclass

I have been tasked with migrating our product's UI to VS2010. It is an MFC app, originally written in VC6. I have performed the following steps:
Converted the VC6 .dsp using VS2010
fixed up compile errors due to stricter VS2010 compiler
Removed all project references to VC6 mfc libs and directories
My problem is that for a dialog object (actually it's a CPropertyPage object), OnInitDialog() is not being called before other methods are. This causes an exception as OnInitDialog() needs to setup member variables.
The dialog class (CPAGEViewDefRecordFields) is subclassed from our own CValidatedPropertyPage, which in turn is derived from the MFC CPropertyPage class. The virtual method OnInitDialog() is present in all subclasses.
In the VS2010 version, when DoModal() is called on the containing property sheet, the OnInitDialog() method of the CPAGEViewDefRecordFields class is not being called. In the VC6 version, it is being called and all works ok.
In VC6, I can see that the message WM_INITDIALOG is sent, and handled in AfxDlgProc(), which in turn then calls OnInitDialog() of the dialog object.
In the VS2010 version, the first message that is processed is WM_NOTIFY, not WM_INITDIALOG.
Unfortunately I have no prior experience in MFC. What I am assuming that something has changed in the behaviour of MFC between the VC6 version and the VS2010 version. However I've not been able to find anything on the net which is similar to this.
Is there another migration step I have missed? Should I have to do something to the resources in the project when doing the migration?
I have checked that the resource is tied to the correct cpp file, as I can double click on the property page, and the IDE takes me to the correct file for the CPAGEViewDefRecordFields class.
If any of you have any ideas, I'd be very grateful.
Thanks!
Chris.
class CPAGEViewDefRecordFields : public CValidatedPropertyPage
{
public:
// Construction
CPAGEViewDefRecordFields(CWnd* pParent,
CXpViewProp* pViewProp,
CFont* pFont = NULL,
UINT nIDCaption = 0,
BOOL bSumOpRequired = TRUE,
BOOL bMinMaxRequired = TRUE,
BOOL bAllRecords = TRUE,
BOOL bShowInitSel = TRUE,
XLong lLimits = 0,
BOOL bSortSelTree = TRUE,
CXpThreshBaseLogProp* pThreshLogProp = NULL);
~CPAGEViewDefRecordFields();
// Dialog Data
//{{AFX_DATA(CPAGEViewDefRecordFields)
enum { IDD = IDD_VIEW_DEF_RECORD_FIELDS };
//}}AFX_DATA
// Overrides
// ClassWizard generate virtual function overrides
//{{AFX_VIRTUAL(CPAGEViewDefRecordFields)
virtual BOOL OnInitDialog();
//}}AFX_VIRTUAL
virtual BOOL OnSetActive();
virtual BOOL OnKillActive();
virtual void OnOK();
protected:
...
// Generated message map functions
//{{AFX_MSG(CPAGEViewDefRecordFields)
afx_msg void OnPbRemove();
afx_msg void OnPbAdd();
afx_msg void OnDblclkAvailableFields(NMHDR* pNMHDR, LRESULT* pResult);
afx_msg void OnDblclkSelectedFields(NMHDR* pNMHDR, LRESULT* pResult);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
private:
...
UPDATE:
After some debugging, I can see what I think is the problem. However, not being an MFC programmer, I don't understand it.
I can see that OnInitDialog() is being called for the property sheet, and that a WM_INITDIALOG is then sent from the property sheet to the property pages. however, at some point in the windows internals, a WM_NOTIFY message is being sent, so this is the first message that is received, not the expected WM_INITDIALOG
I've highlighted the points on the stack trace, attached - can anyone explain why this is occuring? Is this normal behaviour - should I be catering for this in the future?
I've actually found a workaround, and that's to have an initialised flag, so that no code is executed until OnInitDialog() has been called. This is not the best solution, and I fear is more of a hack, so i would still appreciated any understanding of these messages. (I'm not an MFC programmer by trade you see!)
thanks!
OnInitDialog is called after all the dialog controls are created and just before the dialog box is displayed.
Thought I'd better answer this.
The answer came from a SO user's comment:
Your workaround with an initialized flag is the same as I would do. It looks like a tree view sends a notification when the tree view is created but your dialog isn't ready yet. You might not know when other controls do the same thing, so you need an initialized flag
The "workaround" is the only way to guarantee that the dialog is ready.