Solved

CFormView parented by a CTabCtrl

Posted on 1997-06-04
10
1,832 Views
Last Modified: 2013-11-20
I am attempting to create a CTabCtrl and associate a CFormView with each tab to display various statistics.  So far it all works fine, I can launch the app, which creates a CView view, the CView view then creates a CTabctrl control and the control creates each of the associated CFormViews by creating a "CreateContext" object and calling thier "Create" member functions.

I have commented out the creation of all but one tab and its associated CFormView.  (just to make debugging easier)

I do not currently make any DestoryWindow() calls, but I have verified that the framework does call the DestroyWindow functions and destructors correctly.  The problem occurs just after the destructor for the first CFormView is called.

The problem is that when I exit the application I get an ASSERTION FAILED message stating that _BLOCK_TYPE_IS_INVALID(pHead->nBlockUse) which occurs in dbgheap.c.

Does a CFormView have to be parented by a FrameWnd?
Does a CFormView have to be associated with a CDocument?
Can anyone shed any light on the error message?

Some code sipets that might be useful are below (I have not yet made any modifications to the CFormView derived classes):

in the CView derived class' OnInitialUpdate,
{
      CView::OnInitialUpdate();
      
      // TODO: Add your specialized code here and/or call the base class

      // Create the Tab Control
      DWORD dwStyle = TCS_TABS | TCS_SINGLELINE | TCS_RAGGEDRIGHT | WS_CHILD |       WS_VISIBLE;
      
      CRect TabRect;
      GetClientRect(TabRect);

      if (!m_wndTabs.Create(dwStyle, TabRect, this, TAB_CONTROL_ID))
      {
            TRACE0("Failed to create tab control\n");
      }
}


In the CTabCtrl derived class' OnCreate handler:
{
      if (CTabCtrl::OnCreate(lpCreateStruct) == -1)
            return -1;

      // TODO: Add your specialized creation code here
      TC_ITEM TabCtrlItem;
      TabCtrlItem.mask = TCIF_TEXT;
          TabCtrlItem.cchTextMax = 0;
          TabCtrlItem.iImage = 0;

      CRect TabRect;
      GetClientRect(TabRect);

      // for now an temporary size adjustment
      TabRect.top += 30;
      TabRect.right -= 10;
      TabRect.bottom -=30;

      CCreateContext CreateContext;
      CreateContext.m_pNewViewClass = RUNTIME_CLASS(GENERAL_VIEW);
      CreateContext.m_pCurrentDoc = NULL;
      CreateContext.m_pNewDocTemplate = NULL;
      CreateContext.m_pLastView = NULL;
      CreateContext.m_pCurrentFrame = NULL;

      m_general_view.Create(NULL,
                        "",
                        WS_CHILD | WS_VISIBLE,
                        TabRect,
                        this,
                        IDV_GENERAL,
                        &CreateContext);

      TabCtrlItem.pszText = "General";
      InsertItem(0, &TabCtrlItem);
      

      return 0;
}
0
Comment
Question by:YeahRight
  • 6
  • 4
10 Comments
 
LVL 11

Accepted Solution

by:
mikeblas earned 130 total points
Comment Utility
Since you don't provide a stack trace at the time of the assertion, and since you don't mention wihch version of VC++ on which operating system you're using, it's very difficult to give you the right answer on the first try.

What's _probably_ wrong is that you've done a bad job of managing your m_wndTabs member.  This member is a window, and you'll want to take care of its destruction.  When the view window that owns the m_wndTabs member is destroyed, the C++ view object will delete itself. Since the m_wndTabs window is a parent of that view window, the Windows window will still exist while the C++ object has been destroyed.  Eventually, the frame will shut down, and it will try to destroy the tab window object; but since the C++ object is gone, MFC gets sick.

Having a tab control parent any window is questionable.  Certainly, having a C++ object represent a window and contain its parent is very questionable and the root of your problem.

You can work around the problem without fixing your design by handling WM_DESTROY for your view. There, either call DestroyWindow() or Detach() on your m_wndTabs object.  I think you should revisit your design, though.

.B ekiM


0
 

Author Comment

by:YeahRight
Comment Utility
What I am attempting to do is utilize the same view to display various groupings of statistics.  This seems to be represented well by a tab control.  So on each tab "A" I want to display group "A" statistics.  So I figured that associating a view with each tab would be a logical implementation...

1. Does a view have to be parented by a frame?
2. Does a view have to be associated with a Doc?
3. Can you suggest a better design that I should consider?

FYI - here is the call stack from the VC++ 5.0 IDE running on Win95:

_free_dbg_lk(void * 0x0065021c, int 4) line 1017 + 82 bytes
_free_dbg(void * 0x0065021c, int 4) line 970 + 13 bytes
CObject::operator delete(void * 0x0065021c) line 44 + 12 bytes
GENERAL_VIEW::`scalar deleting destructor'(unsigned int 1) + 34 bytes
CView::PostNcDestroy() line 120 + 31 bytes
CWnd::OnNcDestroy() line 815
CWnd::OnWndMsg(unsigned int 130, unsigned int 0, long 0, long * 0x0063ef08) line 1795
CWnd::WindowProc(unsigned int 130, unsigned int 0, long 0) line 1555 + 30 bytes
AfxCallWndProc(CWnd * 0x0065021c {CObject}, HWND__ * 0x00000cfc, unsigned int 130, unsigned int 0, long 0) line 217 + 26 bytes
AfxWndProc(HWND__ * 0x00000cfc, unsigned int 130, unsigned int 0, long 0) line 371
AfxWndProcBase(HWND__ * 0x00000cfc, unsigned int 130, unsigned int 0, long 0) line 203 + 21 bytes
KERNEL32! bff73663()
KERNEL32! bff928e0()

0
 

Author Comment

by:YeahRight
Comment Utility
After following your suggested educated guess as to the resolution of the problem without having all the facts, here is what I found:

When using DestroyWindow():

The calling order of the "OnDestroy()" and "destructors" remains unchanged and that is as follows:

CStatusView::OnDestory (which parents the m_tabWnd)
    CTabs::OnDestroy() (which parents the m_general_view)
       GENERAL_VIEW::OnDestroy()
          GENERAL_VIEW::~GENERAL_VIEW()
                ASSERTION FAILURE


When using Detach():
I get another Assertion Failure on line 386 in WinCore.cpp

LRESULT CALLBACK
AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
      // special message which identifies the window as                 using AfxWndProc
      if (nMsg == WM_QUERYAFXWNDPROC)
            return 1;

      // all other messages route through message map
      CWnd* pWnd =                 CWnd::FromHandlePermanent(hWnd);

*****************************
**** This assert failes ****      
             ASSERT(pWnd != NULL);
********************

      ASSERT(pWnd->m_hWnd == hWnd);
      return AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam);
}

The call stack is as follows:

AfxWndProc(HWND__ * 0x000005d8, unsigned int 2, unsigned int 0, long 0) line 368 + 25 bytes
AfxWndProcBase(HWND__ * 0x000005d8, unsigned int 2, unsigned int 0, long 0) line 203 + 21 bytes
KERNEL32! bff73663()
KERNEL32! bff928e0()

0
 
LVL 11

Expert Comment

by:mikeblas
Comment Utility
Associating a view with a tab is very logical: making it a child of the tab control is not very lgoical at all.  The tab control window and the view window don't have any ownership relationship, and have no reason to be created as parent/child.

Putting an instance of a class into member data of a class when the member is actually a parent of the child class is harder to justify.

Your stack traces are a great step in the right direction: now it's time to use them to debug.  Which window is it that is handling the message before the assertion happens?   You can use Spy++ to figure out what the handle of the tab control is, and the handle of the window being destroyed will show as the first parameter to AfxWndProc() in the callstack you see when you break on the assertion.

What's m_general_view?

For such a small amount of points, I don't usually write code.  But you can add

CTabCtrl m_wndTabs;

to your view's instance data, and then overrde these functions to get what you want:

#define TAB_CONTROL_ID 0x1002

void CBoomView::OnInitialUpdate()
{
   CView::OnInitialUpdate();

   // Create the Tab Control
   DWORD dwStyle = TCS_TABS | TCS_SINGLELINE | TCS_RAGGEDRIGHT | WS_CHILD | WS_VISIBLE;

   CRect TabRect;

   CWnd* pParent = GetParent();
   pParent->GetClientRect(TabRect);
   TabRect.right += 2;

   ASSERT(pParent != NULL && IsWindow(pParent->m_hWnd));

   if (!m_wndTabs.Create(dwStyle, TabRect, pParent, TAB_CONTROL_ID))
      TRACE0("Failed to create tab control\n");
   else
   {
      m_wndTabs.UpdateWindow();

      TC_ITEM TabCtrlItem;
      TabCtrlItem.mask = TCIF_TEXT;
      TabCtrlItem.cchTextMax = 0;
      TabCtrlItem.pszText = "General";

      m_wndTabs.InsertItem(0, &TabCtrlItem);
   }
}

void CBoomView::OnWindowPosChanging(WINDOWPOS* lpwndpos)
{
   if (IsWindow(m_wndTabs))
      m_wndTabs.SetWindowPos(NULL, 0, 0, lpwndpos->cx, 30, SWP_NOZORDER | SWP_NOMOVE);

   lpwndpos->cy -= 30;      // arbitrary height
   lpwndpos->y += 30;

   SetWindowPos(NULL, 0, 31, lpwndpos->cx, lpwndpos->cy, SWP_NOZORDER | SWP_NOSENDCHANGING);
}

void CBoomView::OnDestroy()
{
   m_wndTabs.DestroyWindow();
   CView::OnDestroy();
}

Of course, you don't need that #define, I don't think.  And you need to remember to hook up the appropriate message map entries.

.B ekiM

0
 

Author Comment

by:YeahRight
Comment Utility
I appreciate your help on this...unfortunately I am new to the Expert Exchange and do not have alot of points to offer just yet!

Back to the problem at hand...In your sample code, you have a View which creates tab control.  This is very similar to what I am doing except that I sub-classed the tab control.  But you do not create any additional views for each of the tabs.  What my code does, is to create a new CFormView for each tab.  So to answer you question (What's m_general_view?)...m_general_view is a CFormView derived class that displays a dialog as a result to the "General" Tab being selected.  I also have a m_sscop_view which is a CFormView derived class that displays a dialog as a result of the "SSCOP" tab being press...and so on.  That is why it seemed logical to me to have the tab control parent the CFormView views.

Anyway to answer your other question...(Which window is it that is handling the message before the assertion happens?)

The CFormView derived class "GENERAL_VIEW" (or the class type for m_general_view) is handling the message as the destruction order shows: The order again is as follows:

1. CStatusView::OnDestory
         (CStatusView is the SDI's CView derived class which          parents the m_tabWnd and is a child of CMainFrame)

2. CTabs::OnDestroy()
         (CTabs is a CTabCtrl derived class which parents the                   m_general_view and is a child of CStatusView)

3. GENERAL_VIEW::OnDestroy()
4. GENERAL_VIEW::~GENERAL_VIEW()
        (GENERAL_VIEW is a CFormView derived class and is the         base class for m_general_view and is a child of CTabs)

5. ASSERTION FAILURE

I used Spy++ to verify what was shown in the call stack above and that is that the the Window's window created based on the GENERAL_VIEW base calss was being destroyed at the time when the Assert Failure occured.  NOTE: the destructors for the CStatusView and the CTabs class were not yet called.

0
How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

 

Author Comment

by:YeahRight
Comment Utility
I FIXED IT!

I have made modifications to resolve the assertion failure, but I am still not sure why they worked.

CTabs had as member classes each of the CFormViews that were responsible for displaying each page.  They were defined as:

GENERAL_VIEW m_general_view;
...

The modifications that I made were to change the above definition to:

GENERAL_VIEW *m_general_view;

and in CTabs constructor:

m_general_view = new GENERAL_VIEW();

and I obviously had to use m_general_view as a pointer now..


Two questions:
1. Why does this not present the same assertion failure?
2. Why do I not need to call "delete(m_general_view);" in the CTabs destructor.

0
 
LVL 11

Expert Comment

by:mikeblas
Comment Utility
1) Because you're not deleting the same memory twice.

2) Because CWnd does it for you when the window is destroyed; the window is destroyed automatically because it's a child of another window.

.B ekiM
0
 

Author Comment

by:YeahRight
Comment Utility
I understand why the assert is no longer failing...that part is obvious.  What I don't understand is why, based on the code changes that I made, the attempt to delete the memory does not occur twice!

Also, wasn't CWnd destroying the window and deleteing the memory in my initial source code?
0
 
LVL 11

Expert Comment

by:mikeblas
Comment Utility
I'm sorry: I can only answer the questions that you ask, not the questions that you really mean.  You asked, exactly, "why does this not present the same assertion failure", and that's what I answered.

In your first attempt, the CView window was being destroyed twice; first, because MFC got a WM_POSTNCDESTROY message for it, and second because the destructor of the CTabCtrl-derived class you had called the destructor of the CView-derived class it had as member data.

The second attempt doesn't have the member instance--it just has a member pointer.  So, when the tab-derived class is destroyed, it doesn't destroy again the view-derived class.

Again, having member data that reprsents a parent window is not a very normal design, and I'd strongly recommend that you revisit it.

By the way, how much work will you want me to do before you decide to award some points?

.B ekiM

0
 

Author Comment

by:YeahRight
Comment Utility
And here I thought that you were really interested in helping me...and all along you were just interested in the points...
0

Featured Post

What Is Threat Intelligence?

Threat intelligence is often discussed, but rarely understood. Starting with a precise definition, along with clear business goals, is essential.

Join & Write a Comment

Introduction: Ownerdraw of the grid button.  A singleton class implentation and usage. Continuing from the fifth article about sudoku.   Open the project in visual studio. Go to the class view – CGridButton should be visible as a class.  R…
Introduction: Dialogs (2) modeless dialog and a worker thread.  Handling data shared between threads.  Recursive functions. Continuing from the tenth article about sudoku.   Last article we worked with a modal dialog to help maintain informat…
This video will show you how to get GIT to work in Eclipse.   It will walk you through how to install the EGit plugin in eclipse and how to checkout an existing repository.
In this seventh video of the Xpdf series, we discuss and demonstrate the PDFfonts utility, which lists all the fonts used in a PDF file. It does this via a command line interface, making it suitable for use in programs, scripts, batch files — any pl…

771 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

14 Experts available now in Live!

Get 1:1 Help Now