CTreePropSheetOffice2003 for Office 2003 mode.
CTreePropSheetEx with XP theme enabled.
CTreePropSheetEx derived class with bordered page frame, XP theme non enabled.
CTreePropSheetEx is an evolution above the excellent CTreePropSheet[^] written by Sven Wiegand. It offers the following additional features over the initial class:
- The property sheet is resizable at runtime.
- The navigation tree is also resizable either programmatically or by the user.
- The navigation tree supports the following resizing policies:
- Fixed width,
- Width changes proportionally to the sheet.
- New page frames
CPropPageFrameEx. It introduces flicker free gradient caption. It also offers the option to use the GDI
CPropPageFrameBordered. Based on
CPropPageFrameEx, this class draws a frame around the page even for non-themed applications (see sample picture).
- Skip empty pages.
- Configure help context menus.
- High color icons in tree and tab modes.
- Set minimum pane sizes for the tree and the frame.
- Office 2003 option sheet look and feel.
- Expand all items in tree.
- Enable/disable property page - New in 0.4.
This set of classes uses many components that have been published on CodeProject. See the Acknowledgements section for a full list of the used components.
CTreePropSheetEx is very similar to using
CTreePropSheet. At this point, if you haven’t already done so, I would recommend that you read Sven’s article regarding CTreePropSheet [^]. In the following explanation, I will assume that you already have a property sheet and its pages created. To use the class, you need to:
- Add the following files to your project:
File names (37 files)
CTreePropSheetBordered class, derived from
CTreePropSheetEx, draws a border around the frame when XP theme is not available.
CTreePropSheetOffice2003 class, derived from
CTreePropSheetEx, provides a look and feel similar to Office 2003 option dialogs.
CTreePropSheetBase class, base class for
CTreeCtrl derived allowing custom color for each tree item. Based on CTreeCtrlEx[^].
CPropPageFrame class. Abstract base class for the frame drawn around pages when in tree mode.
CPropPageFrame implementation that provides flicker-free frame. It is the class used by default by
CPropPageFrameBordered class. Derived from
CPropPageFrameEx, extends it by drawing a border around the frame when XP theme is not available.
CPropPageFrameOffice2003 class. Derived from
CPropPageFrameEx, extends it by providing a look and feel similar to Office 2003 option dialogs.
CTreePropSheetSplitter class. Based on CSimpleSplitterWnd[^], implements the splitter class.
|TreePropSheetUtil.hpp ||Misc. utility classes.|
|Helper class for accessing XP theme functions. |
|Implements the resizable library using message hooking rather than inheritance. |
Resizable library[^] classes.
|CHookWnd[^] class. Defines the interface for an MFC class to implement message hooking. |
|memDC.h ||CMemDC[^] class. Implements a memory Device Context which allows flicker free drawing. |
|HighColorTab.hpp||Dynamically updates the tab’s image list to add 16M color icons support. |
Resizable library class for property pages[^]. Not directly used by
CTreePropSheetEx but useful to add resizing capability to property pages.
CTreePropSheetOffice2003 instead of
CPropertySheet. If you already have a class deriving from
CPropertySheet, you need to derive from
CTreePropSheetOffice2003) instead. You also need to replace all references to
CTreePropSheetOffice2003). If you are using a
CPropertySheet object directly, just change its type to
CTreePropSheetOffice2003 are in the namespace
CCriticalSection. This class is defined in <afxmt.h>, therefore you should add the following line to your precompiled header file:
dynamic_cast which means that RTTI support should be enabled.
- If you choose to have the sheet resizable (which is the option enabled by default), you will also have to add resizing support to your property pages. The easiest way to do this is to use Paolo Messina's
CResizablePage class. The following code snippet demonstrates how this is done for one of the property pages in the sample application:
- Change the base class for your property page from
- Edit the
OnInitDialog method of each page in order to add the sizing constraints for the controls inside the page. For instance, in one of the pages in the demo, this looks like this:
AddAnchor(IDC_EMAIL1, TOP_LEFT, TOP_RIGHT);
AddAnchor(IDC_EMAIL2, TOP_LEFT, TOP_RIGHT);
AddAnchor(IDC_EMAIL3, TOP_LEFT, TOP_RIGHT);
AddAnchor(IDC_COMBO_DEFAULT_EMAIL, TOP_LEFT, TOP_RIGHT);
For more information, you should read Paolo's article[^].
- If you are using
CTreePropSheetOffice2003, you need to customize each property page in order to draw a white background (or more accurately, a background with the system color
COLOR_WINDOW). To do so, you need to handle
WM_CTLCOLOR for each property page. This message is sent by each child control to the parent page before it is drawn and let the parent prepare the DC before the control is rendered. The new message handler should be as follows:
HBRUSH CPageDates::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
return ::GetSysColorBrush( COLOR_WINDOW );
This code is a minimal default implementation. You can look at the demo project to see how it is possible to update property pages so that they can be rendered in the 'standard mode' and the Office 2003 mode.
A full reference of the API has been generated using Doxygen[^]. A link is provided at the top of this document.
All the code relating to
CTreePropSheetEx is placed in the namespace
TreePropSheet. This is the same namespace as for
CTreePropSheet. Some of the other classes used by this framework live in their own namespace or in the global namespace.
Two features are set at compile time:
CWnd* pParentWnd = NULL,
UINT iSelectPage = 0);
CWnd* pParentWnd = NULL,
UINT iSelectPage = 0);
The constructors are overrides of the
<PRE lang=c++>BOOL SetTreeViewMode(BOOL bTreeViewMode = TRUE,
BOOL bPageCaption = FALSE,
BOOL bTreeImages = FALSE);
Sets the sheet in tree mode and specifies whether the page caption should be displayed. Also indicates if the tree items should have icons.
bool SetIsResizable(const bool bIsResizable);
bool IsResizable() const;
Enable/disable the sheet resizing feature. The sheet is resizable by default. You must call
SetIsResizable before creating the window (call to
Create) or this call will be ignored and will return
void SetMinSize(const CSize& sizeMin);
CSize GetMinSize() const;
Set/get the minimum size for the sheet.
void SetMaxSize(const CSize& sizeMax);
CSize GetMaxSize() const;
Set/get the maximum size for the sheet.
<PRE lang=c++>void SetPaneMinimumSize(const int nPaneMinimumSize);
bool SetPaneMinimumSizes(const int nTreeMinimumSize, const int nFrameMinimumSize);
int GetPaneMinimumSize() const; // Deprecated. Use GetPaneMinimumSizes instead.
void GetPaneMinimumSizes(int& nTreeMinimumSize, int& nFrameMinimumSize) const;
Set/get the minimum sizes for the panes (the tree and the page).
SetPaneMinimumSizes must be called before creating the window (call to
Create) or this call will be ignored and will return
SetPaneMinimumSize sets the tree and frame minimum size to the value whereas
SetPaneMinimumSizes allows to set individual minimum size values for the tree and the frame.
Note: Since version 0.2, it is recommended to use
GetPaneMinimumSizes rather than
GetPaneMinimumSize as this method does not support the individual minimum sizes and returns
-1 when it is called under this scenario.
Tree resizing feature
void SetTreeIsResizable(const bool bIsTreeResizable);
bool IsTreeResizable() const;
Allow/disallow resizing the tree width with the mouse. The tree is still resizable programmatically as long as the dialog is resizable (
true). The cursor splitter is updated according to the tree resizing capability.
<PRE lang=c++>int GetSplitterWidth() const;
bool SetSplitterWidth(const int nSplitterWidth);
Configure the splitter width. You must call
SetSplitterWidth before creating the window (call to
Create) or this call will be ignored and will return
<PRE lang=c++>void SetTreeResizingMode(const enTreeSizingMode eSizingMode);
enTreeSizingMode GetTreeResizingMode() const;
Describes how the tree should be resized when the sheet is resized. The options are:
TSM_Fixed: The tree width is unchanged when the property sheet is resized. One exception to this is, if the property page has reached its minimum size (minimum pane size) then the tree will shrink.
TSM_Proportional: The tree grows and shrinks proportionally to the property sheet.
virtual BOOL SetTreeWidth(int nWidth);
int GetTreeWidth() const;
Set/get the tree width. Note that the tree width can now be changed even when the sheet windows have been created. However, the sheet has to be resizable (
Splitter update feature
<PRE lang=c++>void SetRealTimeSplitter(const bool bRealtime);
bool IsRealTimeSplitter() const;
Define whether the panes (tree and frame) should be resized in real-time when the user drags the splitter.
Empty pages, empty page text
void SetSkipEmptyPages(const bool bSkipEmptyPages);
bool IsSkippingEmptyPages() const;
Allow/disallow display of empty pages.
void SetEmptyPageText(LPCTSTR lpszEmptyPageText);
DWORD SetEmptyPageTextFormat(DWORD dwFormat);
Set the text content and format for the text to be displayed in empty pages.
BOOL SetTreeDefaultImages(CImageList *pImages);
BOOL SetTreeDefaultImages(UINT unBitmapID, int cx,COLORREF crMask);
Set the icons for the empty pages and pages without icons.
Help context menu configuration feature
void SetContextMenuMode(const enContextMenuMode eContextMenuMode);
TreePropSheet::enContextMenuMode GetContextMenuMode() const;
Define how the help context menus should be handled. The Win32 property sheet displays a 'What's This?' context menu when the user right-clicks on the different controls on the sheet, and provides some appropriate help for the controls that it ‘owns’ such as the OK and Cancel buttons. For the controls added to the sheet (like the tree and the frame), a generic message is shown indicating that no help topic is associated with the item. Since the message is irrelevant, it makes sense not to display the menu at all for these controls. If you have implemented help for these controls, set the value to
TPS_All so that the messages are not trapped.
The options are:
TPS_All: Always display the context menu.
TPS_PropertySheetControls: Display context menu only for the property sheet controls, that is, the menu is not displayed for the tree and frame.
TPS_None: Never display the context menu.
Tree auto expansion
void SetAutoExpandTree(const bool bAutoExpandTree);
bool IsAutoExpandTree() const;
Automatically expand all the items in the tree when it is displayed. You must call
SetAutoExpandTree before creating the window (call to
Create) or this call will have no effect.
Enabling/disabling property pages - New in 0.4
bool EnablePage(const CPropertyPage* const pPage, const bool bEnable);
bool IsPageEnabled(const CPropertyPage* const pPage) const;
The first method allows enabling or disabling the specified property page. The second returns the enabled status of the specified page. It is also possible to set the enable status of a page by sending the following message to the property sheet:
Message ID: WMU_ENABLEPAGE (defined in TreePropSheetBase.h)
WPARAM: Pointer to property page
LPARAM: Enable/disable state
A helper method has also been written to send the message.
hWndTarget is the
hWnd of the property sheet.
inline BOOL Send_EnablePage(HWND hWndTarget,
CPropertyPage* pPage, const bool bEnable)
return (0 != ::SendMessage(hWndTarget,
WMU_ENABLEPAGE, (WPARAM)pPage, (LPARAM)bEnable));
The handle internally calls
Below is a class diagram showing some of the classes used by
Extending the property sheet
All the internal objects used by the class are created using a factory method. This lets you use another class if necessary. The methods are:
virtual CTreeCtrl* CreatePageTreeObject();
virtual CPropPageFrame* CreatePageFrame();
virtual tSplitterPtr CreateSplitterObject() const;
virtual tLayoutManagerPtr CreateLayoutManagerObject();
The following methods let you retrieve the internal object used by the class:
virtual tSplitter* GetSplitterObject() const;
virtual CWnd* GetSplitterWnd() const;
virtual tLayoutManager GetLayoutManagerObject() const;
As mentioned above,
CPropPageFrameEx implements double-buffering to reduce flickering. Also, the class has a new virtual method
DrawBackground which as you might have guessed is called when the frame background needs redrawing.
CPropPageFrameBordered overrides this method to display a border on platforms that do not support XP themes. The method signature is:
virtual void DrawBackground(CDC* pDC);
CPropPageFrameEx now owns and exposes the XP theme helper class so that it can be used by the derived classes. The method to access the helper class is:
const CThemeLibEx& GetThemeLib() const;
Using the bordered frame
The bordered frame (
CPropPageFrameBordered located in PropPageFrameBordered.h and PropPageFrameBordered.cpp) is not used by default. It allows drawing a frame around the active property page when the application does not use XP themes. If you want to use this feature, you need to create a new class that inherits from
CTreePropSheet for that matter), include PropPageFrameBordered.h in your implementation file, and override the method
CreatePageFrame as follows:
return new CPropPageFrameBordered;
Implementation details and points of interest
Creating CTreePropSheetBase rather than reusing existing CTreePropSheet
The main reason for creating
CTreePropSheetBase rather than deriving from
CTreePropSheet was that I needed to update the class (mainly to change the access of some methods and properties). I also made a few fixes to the code. Having the new class also means that you can start using
CTreePropSheetEx without changing any of your code that is using
CThemeLib was inside the CPropPageFrameDefault.cpp file, it was not possible to reuse this code which is why I had to extract it out of this file.
CThemeLibEx is identical to the original class in functionality. I have also changed the
constness of the methods in order to be able to return a
const reference to the class.
The layout manager is integrated with the property sheet using sub-classing rather than inheritance. This way, it is possible to make resizing an option at runtime. When choosing not to have any resizing feature, no layout manager or splitter objects are created.
Skipping blank page
Implementing this feature was straightforward except for the keyboard handling. All the processing is performed in
CTreePropSheetBase::PreTranslateMessage where we can intercept the key strokes before they get routed to the tree control.
Enabling and disabling property pages
Enabling or disabling a property page must be done via the property sheet API (call to
EnablePage) or by sending a
WMU_ENABLEPAGE message. Another solution would have been to add a hook to each property page when they are added to the sheet and handle the
WM_ENABLE message to perform appropriate action. While I believe this is feasible, I went against it because I don't think that many applications are enabling and disabling property pages (I might be wrong) mainly because the sheet control does not give any visual feedback to the user when a page is disabled.
The implementation of the feature required using an extended version of the tree control. This version allows setting the color of a tree item by calling
SetItemColor or sending a
WMU_ENABLETREEITEM message. Using the message mechanism means that the control is still stored as a
CTreeCtrl by the property sheet (in
CTreePropSheetBase). If you already have a custom tree control, you will need to handle
WMU_ENABLETREEITEM in your implementation.
- Any suggestions are welcome. Just let me know so that they can be incorporated in future releases.
- Add an option to use GDI gradient method with fall back to owner drawn if the required library is not available on the system.
I have tested the sample program on Windows 2000/XP and 2003 Server. I will try to test it on Windows 95, 98 and NT 4.0 when I can have access to these platforms.
I have compiled the code on Visual C++ 6.0, Visual Studio 2003 and Visual Studio 2005 Beta.
Win 98 or later and Windows 2000 or later for GDI gradient support.
If you want to enable XP theme, you will also need the platform SDK to compile.
CTreePropSheetEx uses many components that have been published on CodeProject. Many thanks to:
Thank you to the following persons for reporting issues and suggesting fixes:
I hope I haven't forgotten anybody.
- 08/03/2004: 0.1: Initial release.
- 08/14/2004: 0.2: Added features:
CTreePropSheetBordered class added.
- Separate minimum sizes for tree and frame.
- 09/13/2004: 0.3: Added features:
CTreePropSheetOffice2003 class added.
- Auto-expand tree added to
- 09/14/2004: Fixed image size and article minor updates.
- Implemented all suggested bug fixes reported so far,
- Added enabling/disabling of property pages.
The files in the article are released under the terms of the Artistic license. You may obtain a copy of the License here.
Paolo Messina sums up the license quite well by saying that the license “allows for use in commercial applications. You just can't sell this work as part of a library and claim it's yours. This also means that credits are not required, but they would be nice!”