Click here to Skip to main content
15,867,308 members
Articles / Desktop Programming / MFC
Article

CTreePropSheetEx – an extended version of CTreePropSheet

Rate me:
Please Sign up or sign in to vote.
4.98/5 (104 votes)
7 Apr 2005CPOL13 min read 420.9K   17.7K   288   167
CTreePropSheetEx is an extension of CTreePropSheet offering new features such as resizing, skipping empty pages, and new property frames such as Office 2003 option sheet.

CTreePropSheetEx examples

CTreePropSheetOffice2003 for Office 2003 mode.

CTreePropSheetEx examples

Resizing CTreePropSheetEx with XP theme enabled.

CTreePropSheetEx examples

CTreePropSheetBordered, a CTreePropSheetEx derived class with bordered page frame, XP theme non enabled.

Contents

Introduction

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 GradientFill function.
    • 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.

Integration and configuration

How to integrate CTreePropSheetEx

Using CTreePropSheetEx is very similar to using CPropertySheet or 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:

  1. Add the following files to your project:

File names (37 files)

Description

TreePropSheetEx.h
TreePropSheetEx.cpp
CTreePropSheetEx class.
TreePropSheetBordered.h
TreePropSheetBordered.cpp
CTreePropSheetBordered class, derived from CTreePropSheetEx, draws a border around the frame when XP theme is not available.
TreePropSheetOffice2003.h
TreePropSheetOffice2003.cpp
CTreePropSheetOffice2003 class, derived from CTreePropSheetEx, provides a look and feel similar to Office 2003 option dialogs.
TreePropSheetBase.h
TreePropSheetBase.cpp
CTreePropSheetBase class, base class for CTreePropSheetEx.
TreePropSheetTreeCtrl.h
TreePropSheetTreeCtrl.cpp
CTreePropSheetTreeCtrl class, CTreeCtrl derived allowing custom color for each tree item. Based on CTreeCtrlEx[^].
PropPageFrame.h
PropPageFrame.cpp
CPropPageFrame class. Abstract base class for the frame drawn around pages when in tree mode.
PropPageFrameEx.h
PropPageFrameEx.cpp
CPropPageFrameEx class. CPropPageFrame implementation that provides flicker-free frame. It is the class used by default by CTreePropSheetBase and CTreePropSheetEx.
PropPageFrameBordered.h
PropPageFrameBordered.cpp
CPropPageFrameBordered class. Derived from CPropPageFrameEx, extends it by drawing a border around the frame when XP theme is not available.
PropPageFrameOffice2003.h
PropPageFrameOffice2003.cpp
CPropPageFrameOffice2003 class. Derived from CPropPageFrameEx, extends it by providing a look and feel similar to Office 2003 option dialogs.
TreePropSheetSplitter.h
TreePropSheetSplitter.cpp
CTreePropSheetSplitter class. Based on CSimpleSplitterWnd[^], implements the splitter class.
TreePropSheetUtil.hpp Misc. utility classes.
ThemeLibEx.h
ThemeLibEx.cpp
Helper class for accessing XP theme functions.
TreePropSheetResizableLibHook.h
TreePropSheetResizableLibHook.cpp
Implements the resizable library using message hooking rather than inheritance.
ResisableGrip.h
ResizableGrip.cpp
ResizableLayout.h
ResizableLayout.cpp
ResizableMinMax.h
ResizableMinMax.cpp
ResizableMsgSupport.h
ResizableMsgSupport.inl
ResizableState.h
ResizableState.cpp

Resizable library[^] classes.

Hookwnd.h
Hookwnd.cpp
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.hppDynamically updates the tab’s image list to add 16M color icons support.
ResizablePage.h
ResizablePage.cpp

Resizable library class for property pages[^]. Not directly used by CTreePropSheetEx but useful to add resizing capability to property pages.

  1. Use CTreePropSheetEx, CTreePropSheetBordered or CTreePropSheetOffice2003 instead of CPropertySheet. If you already have a class deriving from CPropertySheet, you need to derive from CTreePropSheetEx (or CTreePropSheetBordered or CTreePropSheetOffice2003) instead. You also need to replace all references to CPropertySheet by CTreePropSheetEx (or CTreePropSheetBordered or CTreePropSheetOffice2003). If you are using a CPropertySheet object directly, just change its type to CTreePropSheetEx (or CTreePropSheetBordered or CTreePropSheetOffice2003).

    Note: CTreePropSheetEx, CTreePropSheetBordered and CTreePropSheetOffice2003 are in the namespace TreePropSheet.

    Note: CHookWnd uses CCriticalSection. This class is defined in <afxmt.h>, therefore you should add the following line to your precompiled header file:

    #include <afxmt.h>

    Also, CPropPageFrame uses dynamic_cast which means that RTTI support should be enabled.

  2. 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:
    1. Change the base class for your property page from CPropertyPage to CResizablePage.
    2. 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:
      BOOL CPageEmail::OnInitDialog()
      {
        CResizablePage::OnInitDialog();
      
        // Preset layout
        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);
          
        return TRUE;
      }

      For more information, you should read Paolo's article[^].

  3. 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) 
    {
      pDC->SetBkMode(TRANSPARENT);
      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.

Configuration (API)

A full reference of the API has been generated using Doxygen[^]. A link is provided at the top of this document.

Namespace

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.

Compilation flags

Two features are set at compile time:

  • To use the owner drawn gradient rather than the GDI gradient, uncomment the following line at the top of PropPageFramEx.cpp:
    #define USE_OWNER_DRAW_GRADIANT

    This will give you better compatibility on older operating systems (Win 95 and Win NT 4.0).

  • To enable XP theme (on Win XP and 2003), uncomment the following line in ThemeLibEx.h:
    #define XPSUPPORT

Construction

CTreePropSheetBase();
CTreePropSheetBase(UINT nIDCaption,
                   CWnd* pParentWnd = NULL,
                   UINT iSelectPage = 0);
CTreePropSheetBase(LPCTSTR pszCaption,
                   CWnd* pParentWnd = NULL,
                   UINT iSelectPage = 0);

The constructors are overrides of the CPropertySheet constructors.

Initialization

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.

Resizing feature

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 DoModal or Create) or this call will be ignored and will return false.

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.

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). SetPaneMinimumSize or SetPaneMinimumSizes must be called before creating the window (call to DoModal or Create) or this call will be ignored and will return false.

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 (IsResizable returns true). The cursor splitter is updated according to the tree resizing capability.

C++
int GetSplitterWidth() const;
bool SetSplitterWidth(const int nSplitterWidth);

Configure the splitter width. You must call SetSplitterWidth before creating the window (call to DoModal or Create) or this call will be ignored and will return false.

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 (IsResizable returns true).

Splitter update feature

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.

Default icons

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 DoModal or 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)
{
  ASSERT(::IsWindow(hWndTarget));
  return (0 != ::SendMessage(hWndTarget, 
               WMU_ENABLEPAGE, (WPARAM)pPage, (LPARAM)bEnable));
}

The handle internally calls EnablePage.

Implementation and extension

Below is a class diagram showing some of the classes used by CTreePropSheetEx.

Image 4

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:

CTreeCtrl* GetPageTreeControl();
CPropPageFrame* GetFrameControl();
virtual tSplitter* GetSplitterObject() const;
virtual CWnd* GetSplitterWnd() const;
virtual tLayoutManager GetLayoutManagerObject() const;

Property frames

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);

Also, 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 CTreePropSheetEx (or CTreePropSheet for that matter), include PropPageFrameBordered.h in your implementation file, and override the method CreatePageFrame as follows:

CPropPageFrame* CYourNewClassName::CreatePageFrame()
{
  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 CTreePropSheet.

Since 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.

Layout manager

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 WM_PAINT and WMU_ENABLETREEITEM in your implementation.

Planned enhancements

  • 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.

Requirements

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.

Compiler

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.

Acknowledgements

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.

Version history

  • 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 CTreePropSheetBase.
  • 09/14/2004: Fixed image size and article minor updates.
  • 04/03/2005:
    • Implemented all suggested bug fixes reported so far,
    • Added enabling/disabling of property pages.

License

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!”

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
President ClearSquare Associates
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralPropertyPage icon size. Pin
Atewa21-Mar-06 12:31
Atewa21-Mar-06 12:31 
QuestionHow to get the parent item's page in the treeview? Pin
banbanyy9-Mar-06 0:52
banbanyy9-Mar-06 0:52 
GeneralResizable Wizard bug Pin
Lee Cave16-Feb-06 23:34
Lee Cave16-Feb-06 23:34 
GeneralQuestion of Button Pin
chary80883-Jan-06 21:26
chary80883-Jan-06 21:26 
GeneralRe: Question of Button Pin
Minkyu Ha8-Mar-07 20:23
Minkyu Ha8-Mar-07 20:23 
QuestionWhy does Button OK can't execute the OnCommand? Pin
chary80883-Jan-06 19:34
chary80883-Jan-06 19:34 
QuestionGetCaption() Pin
Daniel B.13-Dec-05 14:22
Daniel B.13-Dec-05 14:22 
GeneralAssert problem in VC 6.0 Pin
bigbird.zp9-Nov-05 16:26
bigbird.zp9-Nov-05 16:26 
Very good one! But here are a problem (Just in debug mode) I don't know where it is - when I load one page from DLL (I build Page in DLL), Assert will happen, CTreePropSheet has no this problem. Anyone tell me what shall I do?
General[Bug] Scrolling a Multiline edit control with lots of text Pin
funvill9-Nov-05 12:40
funvill9-Nov-05 12:40 
GeneralLittle fix when dynamically add/remove page Pin
norbert_barbosa9-Nov-05 0:12
norbert_barbosa9-Nov-05 0:12 
GeneralAnother fix when dynamically add/remove page Pin
norbert_barbosa30-Nov-05 3:56
norbert_barbosa30-Nov-05 3:56 
GeneralOK and Cancel button in modeless pages Pin
Reeves20046-Nov-05 21:06
Reeves20046-Nov-05 21:06 
AnswerRe: OK and Cancel button in modeless pages Pin
Yves Tkaczyk7-Nov-05 5:45
Yves Tkaczyk7-Nov-05 5:45 
GeneralRe: OK and Cancel button in modeless pages Pin
Reeves20047-Nov-05 14:28
Reeves20047-Nov-05 14:28 
GeneralRe: OK and Cancel button in modeless pages Pin
ArvinB19-Sep-06 11:43
ArvinB19-Sep-06 11:43 
QuestionHow to access controls in one page from another page Pin
ciaran000024-Aug-05 11:19
sussciaran000024-Aug-05 11:19 
AnswerRe: How to access controls in one page from another page Pin
Anonymous6-Sep-05 18:24
Anonymous6-Sep-05 18:24 
GeneralRe: How to access controls in one page from another page Pin
Anonymous7-Sep-05 5:32
Anonymous7-Sep-05 5:32 
GeneralMultiple instances of the same property page Pin
TSR21-Jul-05 5:28
TSR21-Jul-05 5:28 
GeneralRe: Multiple instances of the same property page Pin
Yves Tkaczyk21-Jul-05 6:20
Yves Tkaczyk21-Jul-05 6:20 
GeneralRe: Multiple instances of the same property page Pin
TSR21-Jul-05 7:06
TSR21-Jul-05 7:06 
GeneralRe: Multiple instances of the same property page Pin
Yves Tkaczyk21-Jul-05 7:12
Yves Tkaczyk21-Jul-05 7:12 
GeneralRe: Multiple instances of the same property page Pin
Mathieu DEGROUX20-Oct-06 5:52
Mathieu DEGROUX20-Oct-06 5:52 
GeneralProblem in view Pin
Brad Bruce15-Jun-05 2:41
Brad Bruce15-Jun-05 2:41 
GeneralRe: Problem in view Pin
Yves Tkaczyk15-Jun-05 14:36
Yves Tkaczyk15-Jun-05 14:36 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.