Microsoft & .NETVisual C#Deriving CWnd classes from CCtrlView

Deriving CWnd classes from CCtrlView

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

This article was contributed by Tom Archer.

Introduction

One pretty cool little class theat Microsoft introduced back in version 4 of MFC was the CCtrlView class. However, it’s still 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 version 4 was released, I still hear lots of incorrect statements regarding how a class like the CListView class works internally and I continue to see lots of code 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

screen shot

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

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories