Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Property Sheet View

0.00/5 (No votes)
11 Jun 2001 2  
A "Property Sheet"-like view class for MFC.

[Sample 1 Image - 27K]

fig. 1: Sample with normal dialog template

[Sample 2 Image - 26K]

fig. 2: Sample with a resizable dialog template

Version 1.3

This version includes the following new features and fixes:

  • Made construction methods more compatible with CPropertySheet in order to permit arrays of pages
  • Added demo project for SDI application

Version 1.2

This version includes the following new features and fixes:

  • Made CPropertyView::PreCreateTabControl virtual
  • Fixed a potential crash in CPropertyView::OnSize
  • Improved page name handling (more methods)
  • Made the base class of CPropertyView customizable

Version 1.1

This version includes the following new features and fixes:

  • Support for scrolling.
  • Drawing and sizing stuff now works correctly if the tab control works with tabs at bottom (TCS_BOTTOM).
  • The message filter doesn't kill some important keystrokes (like CTRL+V) any more.

Introduction

Sometimes it may be very useful to organize the data of your document in a view that behaves like a property sheet. While writing an application that permits a user to modify the properties of very complex objects, I created a generic CView-derived class that permits, to create with very little work, a "Property Sheet"-like representation of the data inside a view.

In my implementation of a "Property Sheet"-like view, I have tried to reflect the interface of the standard classes CPropertySheet and CPropertyPage, in order to keep the usage of those classes very simple. This view behaves like a property sheet, providing also support for Ctrl+TAB, Shift+Ctrl+Tab and all typical keyboard combinations to switch between the pages.

Although they are very similar to their examples, there are some slight differences in the usage of some methods. These differences are based mainly upon the different data handling concepts between a dialog and a view: in a modal dialog, the data is edited in a modal window that will be closed by a specific action of the user (e.g., clicking OK or Cancel). On the other hand, a view is a living representation of the data stored in a document. While modifying the data in the view, the document is also modified and the framework permits binding of more than one view to a document. An optimal implementation of a view either updates itself if it receives the signal that the document content was changed by another view, or signals the change to the other views when the user modifies the data.

Usage

First of all, create a dialog template for each page of your view, like if you were creating pages for a property sheet. As I have tested it, there is no limitation in the type of controls that you can place on your page. As in a property page, the caption of each dialog template will become the text of the corresponding tab. Feel free to use accelerator mnemonics in the static members, since they will work correctly.

Use Class Wizard to create a class derived from CDialog for each dialog template. Now we need to replace some things in our source code:

  • Derive your view from CPropertyView instead of CView
  • Derive your dialogs from CPropertyViewPage instead of CDialog

It is a very good idea to do this by a case sensitive search and replace, since in this manner also, all special inline comments for Class View and Class Wizard will be correctly updated.

Now exit your workspace, delete the .ncb and .clw files of your project, reopen the workspace and regenerate the .clw file by invoking Class Wizard. By default, each automatically-generated view contains an implementation for the methods OnDraw and PreCreateWindow. You can safely remove them both from your view class.

Typically, a property sheet defines its pages as member variables and calls the AddPage method in its constructor. The property sheet then creates the pages only if the user switches on a page by selecting the corresponding tab. This is one of the considerable differences in my CPropertyView implementation: by calling AddPage, the corresponding page is immediately created. This means that AddPage can only be called if the view still exists. A good place to do this is in the OnCreate method of the view:

int CTabSample1View::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
    if ( CPropertyView::OnCreate(lpCreateStruct) == -1 ) 
    {
        TRACE(_T("Failed to create the property view\n"));
        return -1;
    }

    if ( !m_ImgList.Create (IDB_TABICONS, 16, 1, RGB(0, 255, 255)) ) 
    {
        TRACE(_T("Failed to create the image list\n"));
        return -1;
    }

    SetImageList (&m_ImgList);

    AddPage (&m_pageGeneral, 0);
    AddPage (&m_pagePersonal, 1);

    return 0;
}

In this example, you can see another slight difference between CPropertySheet and CPropertyView: The method SetImageList is used to specify an image list for the tab icons and the method AddPage can contain an additional parameter specifying the index of the used image from the image list.

CPropertyView implements nearly all methods of CPropertySheet except for these:

  • Construct (not needed)
  • SetTitle (it makes no sense in a view)
  • SetFinishText (it also makes no sense in a view since there are no OK, Apply or Cancel buttons)
  • SetWizardButtons (see above)
  • SetWizardMode (see above)
  • DoModal (see above)
  • EndDialog (see above)

No other work has to be done in the view class, since all typical overrides for a view are implemented in CPropertyView and they are passed as needed to the specific page classes.

Our pages are implemented as classes derived from CPropertyViewPage. This class is nearly identical in its interface to CPropertyPage with a few slight differences. Also here, some methods are not available:

  • Construct (not needed)
  • CancelToClose (Since there are no buttons, it makes no sense)
  • QuerySiblings (I have never used it. If needed, I will implement it)
  • OnCancel (There is no such situation)
  • OnReset (see above)
  • OnQueryCancel (see above)
  • OnWizardBack (see above)
  • OnWizardNext (see above)
  • OnWizardFinish (see above)

Unlike a property sheet, OnInitDialog is not the right place to initialize the controls in your page with the contents. Override OnInitDialog only if you need to configure your controls on a one-time basis like specifying an image list, setting a font or filling a combo box with a fixed number of choices (the data in your document is the index of the selected choice, not the choices themselves).

For this purpose, CPropertyViewPage implements also all typical functions of a view, like OnInitialUpdate and OnUpdate. After the view (and all its pages) are successfully created, the OnInitialUpdate function is also called for each existing page. The default implementation calls OnUpdate (NULL, 0, NULL) like in CView. If you wish to support dynamic update between different views of your document, you also need to implement OnUpdate and obviously call UpdateAllViews on each modification of the document.

Here is an example on how to initialize the controls with the contents of the document:

void CPageGeneral::OnInitialUpdate ()
{
    CTabSample1Doc *pDoc = ((CTabSample1View *) GetView ())->GetDocument ();
    m_csFirstName   = pDoc->m_csFirstName;
    m_csLastName    = pDoc->m_csLastName;
    m_csAddress     = pDoc->m_csAddress;
    m_csZipCode     = pDoc->m_csZipCode;
    m_csCity        = pDoc->m_csCity;
    m_csCountry     = pDoc->m_csCountry;
    m_csRemarks     = pDoc->m_csRemarks;

    // write back to the controls

    UpdateData (FALSE);
}

When saving the document, we also need a method that permits saving changed data in the window controls to the document. Saving a document is somewhat similar to clicking the "Apply" button in a property sheet. In each page, the OnApply method should be implemented to copy the data back to the document:

BOOL CPageGeneral::OnApply ()
{
    if ( !CPropertyViewPage::OnApply () ) 
    {
        return FALSE;
    }

    CTabSample1Doc *pDoc = ((CTabSample1View *) GetView ())->GetDocument ();
    pDoc->m_csFirstName = m_csFirstName;
    pDoc->m_csLastName  = m_csLastName;
    pDoc->m_csAddress   = m_csAddress;
    pDoc->m_csZipCode   = m_csZipCode;
    pDoc->m_csCity      = m_csCity;
    pDoc->m_csCountry   = m_csCountry;
    pDoc->m_csRemarks   = m_csRemarks;

    return TRUE;
}

We also need to modify our document to call the OnApply method. A good place to do this is in the override OnSaveDocument:

BOOL CTabSample1Doc::OnSaveDocument(LPCTSTR lpszPathName) 
{
    POSITION pos = GetFirstViewPosition ();
    if ( pos ) 
    {
        CPropertyView *pView = (CPropertyView *) GetNextView (pos);
        if ( pView ) 
        {
            ASSERT_KINDOF(CPropertyView,pView);
            if ( !pView->Apply () ) 
            {
                return FALSE;
            }
        }
    }

    return CDocument::OnSaveDocument(lpszPathName);
}

If you support more than one view per document in your application, you must obviously implement quite a different approach. You must call Apply either when a property view loses the focus (in order to update the document) or in OnSaveDocument for the last view that has the focus.

More Advanced Usage

The second example shows a more advanced usage based on the very nice resizable dialog classes programmed by Hans B�hler. Since the use of a modified dialog class is very probable, the base class of CPropertyViewPage is the define baseCPropertyViewPage set by default to CDialog. This has the advantage that you can easily customize your CPropertyViewPage with your favorite dialog-based class. To change the base class of CPropertyViewPage, you simply need to redefine baseCPropertyViewPage. A good place to do this is in StdAfx.h:

// ...


#include "cdxCSizingDialog.h"


#define baseCPropertyViewPage	cdxCSizingDialog

//{{AFX_INSERT_LOCATION}}

// Microsoft Visual C++ will insert additional

// declarations immediately before the previous line.

In the second example, the implementation of OnInitDialog for each page configures the resizing characteristics of each control in the property page:

BOOL CPageGeneral::OnInitDialog() 
{
    // do not add a size gripper to the dialog box

    CPropertyViewPage::OnInitDialog (10, m_InitFreedom, FALSE);

    // configure the controls to resize properly

    AddSzControl (m_ctrlFirstName, mdResize, mdNone);
    AddSzControl (m_ctrlLastName, mdResize, mdNone);
    AddSzControl (m_ctrlAddress, mdResize, mdNone);
    AddSzControl (m_ctrlCity, mdResize, mdNone);
    AddSzControl (m_ctrlCountry, mdResize, mdNone);
    AddSzControl (m_ctrlRemarks, mdResize, mdResize);

    return TRUE;
}

The resizable dialog class included in the second sample is neither the last version of Hans B�hler's classes nor the original version. I had to make some modifications since his cdxCSizingDialog did not implement all constructors of CDialog. The last version of these classes I found had some more features and a slightly modified interface. I did not have the time to test it in conjunction with my property view classes, but someone told me that it did not work correctly. In any case, the included version works very well for me, so I felt no desire to update to the latest version. You can find the original classes in the article Dynamic child window positioning.

Essential Reference

Since the usage is very similar to a standard property sheet and a standard view, I will document only some methods specific to these classes.

virtual void CPropertyView::PreCreateTabControl (CRect &rect, DWORD &dwStyle);

Override this function if you wish to modify the style of the tab control.

virtual void CPropertyView::SetModified (BOOL bChanged = TRUE)

Override this function to provide special handling in case of modification. The default implementation calls GetDocument()->SetModifiedFlag(bChanged).

CImageList* CPropertyView::GetImageList() const;

This function returns a pointer to the image list associated with the tab control.

CImageList* CPropertyView::SetImageList (CImageList *pImageList)

This function sets an image list for the tab control.

void CPropertyView::EnableScrollView (BOOL bScrollView);

By default, the property view enables dynamic scroll bars that appear if the view is sized smaller than needed to display the active property page. To disable or enable this feature, use this function.

virtual void CPropertyViewPage::SetModified (BOOL bModified = TRUE)
virtual BOOL CPropertyViewPage::GetModified();

You can override these two functions to provide your own implementation of modification handling. The default implementation calls CPropertyView::SetModified (bModified) or returns the current modification state.

History

  • 17 May 2001 - updated source and demos
  • 12 June 2001 - updated source and demos

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here