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

CTreePropSheetEx � an extended version of CTreePropSheet

0.00/5 (No votes)
7 Apr 2005 12  
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.hpp Dynamically 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

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.

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.

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.

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

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.

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