Turn Any CWnd-Derived Control Class into a View


This article was contributed by Tom Archer.

Introduction

One pretty cool little class that Microsoft introduced back in version 4 of MFC was the CCtrlView class. However, it amazes me to see how misunderstood and underutilized this class is with regards to being used as a base class. In other words, even now, some 4 years after this class first saw the light of day, I still hear lots of incorrect statements regarding how a class such as the CListView works internally, I also continue to receive many article submissions where in order to make a control emcompass the entire client area of given frame, the programmer goes through all sorts of unnecessary work to make that happen (e.g., embedding the control in the view, manually doing the sizing code, etc.) Therefore, I decided to dig out an article I wrote some time ago that illustrates exactly how to derive your own CWnd classes from CCtrlView in order to 1) have the control take up the entire client area of a frame and 2) have the control behave as a view in the document/view scheme of things.

General Misconceptions

Let's take an example of a CCtrlView-dervied class: CListView. The first misconception that I hear revolves around the fact that there are two MFC classes that deal with the Explorer like listing of objects: CListCtrl and CListView. Contrary to what a lot of beginners might logically think, these two classes do not encapsulate two different Windows classes. They both deal with one and only one Windows class called a ListView. In fact, the CListCtrl is the encapsulation of the ListView. So why didn't the MFC designers call it a CListView? Because even though the common controls team decided to call their window class a ListView, the suffix "View" has a very specific meaning to us MFC developers and conjures up images of working within the context of the document/view architecture. Therefore, the MFC team decided to instead call it a CListCtrl since to us it is a "control" and not a "view" (in our way of thinking of views).

The next misconception is that the CListView is a CView-derived class that contains an embedded CListCtrl object. This misconception is certainly the result of two things: 1) it would be natural to think of a view as containing a control and 2) the CListView has a member function called GetListCtrl which returns a (seemingly contained) CListCtrl reference.

However, what is happening here (and you'll see this code shortly) is that when you open a view with MFC (typically associated with a given SDI or MDI document template), the MFC framework creates a frame for you and then within that frame creates a window. It is that window that serves as your view and what you perceive as the window's client area. However, with the CCtrlView-derived classes (such as CListView) what is happening is that you can specify any type of window to be used as the client area! In the case of the CListView class, that class' author simply specified that a ListView window class be used and voila! when the view comes up its entire client area is taken up by a ListView and the framework automatically handles the different things such as making sure that the ListView resizes appropriately when the frame is resized.

How it Works

Now let's look at the code of how the CListView gets created so that we can see how this thing works.

The first thing we'll look at is the CListView constructor (found in AFXCVIEW.INL). As you can see below, the CListView constructor simply calls the CCtrlView constructor and passes it a value of WC_LISTVIEW.

_AFXCVIEW_INLINE CListView::CListView() 
: CCtrlView(WC_LISTVIEW, AFX_WS_DEFAULT_VIEW)
{ }

A little more digging turns up the following in the COMMCTRL.H file.

#ifdef _WIN32

#define WC_LISTVIEWA            "SysListView32"
#define WC_LISTVIEWW            L"SysListView32"

#ifdef UNICODE
#define WC_LISTVIEW             WC_LISTVIEWW
#else
#define WC_LISTVIEW             WC_LISTVIEWA
#endif

#else
#define WC_LISTVIEW             "SysListView"
#endif

So now all we know is that the CListView constructor is going to pass a value of "SysListView32" to the CCtrlView constructor. From there let's dig a bit deeper to how simple this whole thing really is.

CCtrlView::CCtrlView(LPCTSTR lpszClass, DWORD dwStyle)
{
 m_strClass = lpszClass;
 m_dwDefaultStyle = dwStyle;
}

BOOL CCtrlView::PreCreateWindow(CREATESTRUCT& cs)
{
 ASSERT(cs.lpszClass == NULL);
 cs.lpszClass = m_strClass;

 // initialize common controls
 VERIFY(AfxDeferRegisterClass(AFX_WNDCOMMCTLS_REG));
 AfxDeferRegisterClass(AFX_WNDCOMMCTLSNEW_REG);

 // map default CView style to default style
 // WS_BORDER is insignificant
 if ((cs.style | WS_BORDER) == AFX_WS_DEFAULT_VIEW)
  cs.style = m_dwDefaultStyle & (cs.style | ~WS_BORDER);

 return CView::PreCreateWindow(cs);
}

Thus the CCtrlView class creates a window using the class name that is passed to its constructor. Because this class is derived from CView, the window in question will have a frame placed around it automatically when the view is created as part of an SDI or MDI document template. It will also automatically be resized to always encompass the entire frame when the frame is resized (alleviating you from writing the sizing code to handle that event).

Creating Your own CCtrlView Classes

So now that you know that bit of MFC triva, how in the world does that help you in the "real world"? Now that you know how this works, you can see that MFC has only used a handful of controls with CCtrlView (e.g., CListView, CTreeView, CEditView, etc.). But let's imagine that you have another control that want to act similarly. For example, let's say that you wanted a ListBox to cover the entire client area and you didn't want to muck around with the sizing code when the view gets resized. Using the CCtrlView class this is incredibly easy. (Note: I used the standard ListBox control here since that can be tested by everyone. You can easily apply these same rules to your own custom CWnd-derived classes as well.)

CListBoxView Source Code

To create something we'll call a CListBoxView, simply declare the class as follows. (This is also included in the downloadable demo below)
#pragma once

class CListBoxView : public CCtrlView
{
 DECLARE_DYNCREATE(CListBoxView)

// c'tor
public:
 CListBoxView();

// Attributes
public:
 CListBox& GetListBox() const;

 //{{AFX_MSG(CListBoxView)
 //}}AFX_MSG
DECLARE_MESSAGE_MAP()
};

Next, implement the source as follows.

#include "stdafx.h"
#include "ListBoxView.h"

IMPLEMENT_DYNCREATE(CListBoxView, CCtrlView)

CListBoxView::CListBoxView() : 
 CCtrlView(_T("LISTBOX"), AFX_WS_DEFAULT_VIEW) { };

CListBox& CListBoxView::GetListBox() const 
{ return *(CListBox*)this; }

BEGIN_MESSAGE_MAP(CListBoxView, CCtrlView)
 //{{AFX_MSG_MAP(CListBoxView)
 ON_WM_NCDESTROY()
 //}}AFX_MSG_MAP
END_MESSAGE_MAP()

Take note of the constructor's initializer list and the fact that it passes the class name of the ListBox control to the CCtrlView constructor. By doing so, this class we call a CCtrlView reveals that it's a ListBox control at heart.

The only function of any interest is the GetListBox function. As you can see, this function simply returns a casted pointer to itself! How can you return a view pointer when the function requested a ListBox pointer?! Remember that almost all MFC control classes are really nothing more than very thin wrappers for their associated Windows classes. As an example, when you call the CListBox::AddString member function, the CListBox object simply sends a Windows message to its associated window to add the specified string:

_AFXWIN_INLINE int CListBox::AddString(LPCTSTR lpszItem)
 { ASSERT(::IsWindow(m_hWnd)); 
   return (int)::SendMessage(m_hWnd, 
                             LB_ADDSTRING, 
                             0, 
                             (LPARAM)lpszItem); }

Therefore, if a user of a CListBoxView requests a CListBox pointer in order to call CListBox member functions this will work because even though they are not the same object in memory, they do both have similar layouts in memory (both being derived from CWnd and therefore, having m_hWnd member variables). Because of this fact, the AddString function will work because the message (shown above) is simply being sent to the m_hWnd associated with the CListBoxView. Needless to say, you have to be very careful because if you attempted to call a CListBox member function that dealt with instance data not associated with your CListBoxView you will certainly GPF!

Using the CListBoxView Class

When you've finished you'll see how easy it is to use any CWnd-derived class as a view in your MFC applications

Testing this class is as easy as writing it. Simply follow these steps ( remember that a demo is included with this article if you don't want to follow along):

  1. Use the AppWizard to create an SDI application called LbxViewTest
  2. Add the ListBoxView.cpp and ListBoxView.h files from above to your new project
  3. Open the LbxViewTestView.h file and include the ListBoxView.h file
  4. Replace all occurrences of CView with CListBoxView
  5. Open the LbxViewTestView.cpp file and replace all occurrences of CView with CListBoxView
  6. Add the view's OnInitialUpdate member function as follows:
    void CLbxViewTestView::OnInitialUpdate() 
    {
     CListBoxView::OnInitialUpdate();
    
     CListBox* pLbx = (CListBox*)this;
     if (pLbx)
     {
      CString str;
      for (int i = 0; i < 10; i++)
      {
       str.Format("Test %ld", i);
       pLbx->AddString(str);
      }
     }	
    }
    

Downloads

Download demo project - 19 Kb


About the Author

Tom Archer - MSFT

I am a Program Manager and Content Strategist for the Microsoft MSDN Online team managing the Windows Vista and Visual C++ developer centers. Before being employed at Microsoft, I was awarded MVP status for the Visual C++ product. A 20+ year veteran of programming with various languages - C++, C, Assembler, RPG III/400, PL/I, etc. - I've also written many technical books (Inside C#, Extending MFC Applications with the .NET Framework, Visual C++.NET Bible, etc.) and 100+ online articles.

Comments

  • Related articles

    Posted by wkrol on 09/04/2004 02:58pm

    The approach described here, only works in some cases. The articles listed below describe a more general approach (namely, putting a control in a CView-derived class, and handling OnSize events.) "How to Create a Custom View to Wrap Your Own Control" Charles Calvert, January 30, 2000 http://www.codeguru.com/Cpp/W-D/doc_view/controlviews/article.php/c3269/ MSDN Magazine Nov. 2001, C++ Q&A, "Understanding CControlView, [...]" Paul DiLascia http://msdn.microsoft.com/msdnmag/issues/01/11/c/default.aspx

    Reply
  • Can CDialog be used ...

    Posted by Legacy on 08/27/2003 12:00am

    Originally posted by: Leo

    I want to use CDialog in the constructor...
    
    Something like

    CDlgCtrlView::CDlgCtrlView():CCtrlView(_T("CDialog"), AFX_WS_DEFAULT_VIEW)
    {
    // TODO: add construction code here

    }

    But this says cannot open empty document !
    What needs to be done ?
    Any alternate method ??

    Basically I want to create Compose View as provided by
    Outlook Express.

    Reply
  • MFC PROGRAMMERS Sourcebook

    Posted by Legacy on 06/09/2003 12:00am

    Originally posted by: AC

    is it possible to make this with multiple list like a standard vb listview in report mode
    with seperate columns

    Reply
  • Does it work with Rich Edit control?

    Posted by Legacy on 01/06/2003 12:00am

    Originally posted by: C Wang

    I failed to use the same method with two rich edit controls in SDI splitter windows. All I got is transparent windows which I can write nothing in. It seems like the CCtrlView constructor can not create rich edit control correctly.

    Reply
  • How do you use your own dervied TreeCtrl?

    Posted by Legacy on 03/07/2002 12:00am

    Originally posted by: Chris Jordan

    Very good article, but let's say you've got your own CTreeCtrl dervied class which handles all your extra functionality How do you use that instead of the standard CTreeCtrl in your CTreeView derived class?

    Do you simply cast to your own derived control class instead?

    Cheers,
    -Chris.

    Reply
  • humm... so that is the difference between CCtrlView and CFrameWnd?

    Posted by Legacy on 08/27/2001 12:00am

    Originally posted by: soichi hayashi

    Humm.. if CCtrlView is basicly the frame providing the functionality of re-sizing the view inside the frame, what is the benefit of having CFrameWnd? This article made me think about it..

    Reply
  • Owner-drawn, "Custom" - derived

    Posted by Legacy on 08/11/2001 12:00am

    Originally posted by: JohnCz

    Paul Elliott:
    You have to handle WM_DRAWITEM.

    Since you have created "view" out of it, you will receive reflection from a control rather than notification message.
    Use ON_WM_DRAWITEM_REFLECT macro instead OnNotify.


    Sreedevi G. and YiHai:
    Windows class: WC_TREEVIEW.

    Just change your base class to CCtrlView.
    If you want to use CTreeCtrl functionality write GetTreeCtrlEx and return (CVTreeView)this. Call all functions of CTreeView class using this pointer.

    Transfer all functionality from CVTreeView to your new view class.

    Reply
  • Good article,but ...

    Posted by Legacy on 08/08/2001 12:00am

    Originally posted by: YiHai

    Hello,Tom:
    Good article,but ...I have the same question like the previous two guys.Pls help me!!

    yihai

    Reply
  • Using CCtrlView for custom Controls...

    Posted by Legacy on 07/13/2001 12:00am

    Originally posted by: Sreedevi G.

    I have a class "CVStaticTreeCtrl" derived from the standard CTreeCtrl. I want a CVTreeView derived from CCtrlView corresponding to the CVStaticTreeCtrl.

    What is the class name that I have to specify in the constructor of CCtrlView?

    CVTreeView::CVTreeView() :
    CCtrlView(??, AFX_WS_DEFAULT_VIEW) { };

    How and when should I register a class name for the CVStaticTree?

    Thank you in advance

    Reply
  • owner draw control?

    Posted by Legacy on 07/12/2001 12:00am

    Originally posted by: Paul Elliott

    Does it work for owner draw control?
    
    

    I've done something very similiar for tab controls using CTabCtrl e.g.

    class CTabView : public CCtrlView{
    CTabView() :
    CCtrlView (WC_TABCONTROL, AFX_WS_DEFAULT_VIEW) {}
    // ETC
    //ETC

    CTabCtrl& GetTabCtrl ()
    const { return *(CTabCtrl*) this; }

    //ETC
    //ETC
    }

    But I can't get it to work for owner draw controls e.g.

    CTabCtrlEx (which allows the text on a tab label to be coloured)

    Any ideas?


    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • As all sorts of data becomes available for storage, analysis and retrieval - so called 'Big Data' - there are potentially huge benefits, but equally huge challenges...
  • The agile organization needs knowledge to act on, quickly and effectively. Though many organizations are clamouring for "Big Data", not nearly as many know what to do with it...
  • Cloud-based integration solutions can be confusing. Adding to the confusion are the multiple ways IT departments can deliver such integration...

Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date