fig. 1: Sample with normal dialog template
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;
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
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()
{
CPropertyViewPage::OnInitDialog (10, m_InitFreedom, FALSE);
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