Professional UI Solutions
Site Map   /  Register
 
 
 

Prof-UIS Property Grid

Download the SimpleProperties sample project, 50 kb Download the SimpleProperties sample project associated with this article (zipped source code, 50 kb)

C++/MFC Prof-UIS Property Grid for browsing and editing properties of objects of any complexity

Figure 1.Sample applications demonstrating the Prof-UIS property grid

Contents

Introduction

The Prof-UIS property grid control provides you with a flexible and elegant tool for implementing a modern property grid like that available in Visual Studio .NET/2005. It is designed for C++/MFC developers and can be used both in Prof-UIS-based projects (most of the windows are controlled by Prof-UIS classes) or in MFC projects. The major features that distinguish the Prof-UIS property grid are as follow:

  • Carefully considered design and active use of Prof-UIS grid classes makes the property grid an easily customizable and extensible tool.
  • Support for browsing and editing properties of more than one object at same time. If the property grid is bound to several objects and a particular property has different values for these objects, the property is displayed as indeterminate. You can set a new value for this property in this mode and all these objects will have the same value for this property.
  • A set of property types that meet the requirements of most of the projects. Due to the flexible architecture, this list can be easily extended.
  • GUI Consistency. The property grid is consistent with other GUI themes available in Prof-UIS like Office 2000/XP/2003 and Visual Studio 2005.
  • A great number of ready-to-use settings that you can adjust to suit your preferences.

Prof-UIS provides two sample projects that demonstrate how to use dynamic control bars: PropertyGrid and CompoundProperties. Besides you can download a little SimpleProperties project that is a result of step-by-step instructions provided in this article (includes intemediate projects for each step).

Design and implementation

The Prof-UIS property grid control window is instantiated and controlled with the CExtPropertyGridCtrl class. The visible parts of the property grid window are associated with their corresponding classes as it is depicted in Figure 2 and Figure 3.

C++/MFC Prof-UIS Property Grid window elements and their corresponding Prof-UIS classes

Figure 2. Property grid window elements and their corresponding Prof-UIS classes

C++/MFC Prof-UIS Property Grid compound properties

Figure 3. Compound properties

The class hierarchy, which reflects all the classes used in the property grid up to CWnd, is shown in Figure 4.

C++/MFC Prof-UIS Property Grid class diagram

Figure 4. Class hierarchy for the Prof-UIS property grid control

Below we will describe the classes that are typically used when working with the property grid.

Property item

The CExtPropertyItem class implements the functionality common to property categories (CExtPropertyCategory) and property values (CExtPropertyValue) and it is a parent class for these two classes. CExtPropertyItem should never be instantiated in your code directly.

Property value

The CExtPropertyValue class implements the property value in the hierarchical list of data. Visually, the property value is divided into the left part, which shows the property value name, and the right part, which displays the value itself and allows the user to edit the value. CExtPropertyValue is inherited from CExtPropertyItem and additionally provides the functionality specific to property values. It keeps two CExtGridCell objects corresponding to active and default values of the property. Storing these two values allows the user to reset the active value to the default one by clicking the Reset item in the context menu that is invoked over the name part of the property value.

CExtGridCell is a base class for the number of cell classes implementing particular cell types, which can be used for property values:

Grid cell class Cell type
CExtGridCellBool Boolean values
CExtGridCellCheckBox Check box control
CExtGridCellColor Color selection control
CExtGridCellComboBox Combo box control
CExtGridCellDateTime Date/time selection control
CExtGridCellDropListComboBox Combo box control without in-place activated edit; left button clicking on any cell area shows a pop-up list box window
CExtGridCellFile File selection control
CExtGridCellFileImage Icon selection control
CExtGridCellFolder Folder selection control
CExtGridCellFont Font selection control
CExtGridCellFontFaceName Font name only selection control
CExtGridCellHyperLink Hyperlink control
CExtGridCellIPAddress IP address control
CExtGridCellPassword Password control
CExtGridCellRadioButton Radio button control
CExtGridCellString Edit control
CExtGridCellUpDown Up-down control for numeric values
CExtGridCellUpDownColorPart Up-down control for the red, green or blue color attribute
CExtGridCellUpDownFontWeight Up-down control for the font weight attribute
CExtGridCellUpDownRectPart Up-down control for rectangle attributes
CExtGridCellVariant Universal cell type that can be use for storing and editing data of different types

Typically you create a CExtPropertyValue-derived class in which you store a pointer to the bound object (whose properties are displayed and set up with the property grid), specify the value name, create a grid cell of the required type in the constructor, and implement the Apply method that should do some specific action related to the bound object.

Compound property value

The CExtPropertyValueCompound class implements the compound property value in the hierarchical list of data. Visually, the compound property value looks like a simple property value but it serves as the parent item for a set of child property values describing its parts. CExtPropertyValueCompound is inherited from CExtPropertyValue. It keeps an array of child property values which can be either simple property values or compound property values.

Mixed property value

The CExtPropertyValueMixed class implements a property value in the combined property store. This property value contains an array of pointers to other property values which are stored in other property stores whose values are displayed in the property grid. Applying a cell value to the mixed property value causes all property values from the property stores that are involved in the combine operation will have the equal value.

Mixed combined property value

The CExtPropertyValueMixedCombined class is derived from the CExtPropertyValueMixed class and implements the mixed property value which contains a reference to combined property values.

Property category

The CExtPropertyCategory class implements a property category. It is derived from CExtPropertyItem and additionally supports an unspecified number of child elements, which can be either property categories and/or property values. This allows you to design the property grid with a category tree of any complexity.

Property store

The CExtPropertyStore class implements the root item in a tree data structure with CExtPropertyItem objects (which describe property categories and values). You can compare a property store with a document, and the property grid window (which is based in the CExtPropertyGridWnd class) as a view in terms of the document-view architecture.

Typically you declare an instance of the CExtPropertyStore class in the class that represents one or more objects whose properties are displayed in the property grid. Then you simply add the required categories and values to this instance somewhere in the code before a pointer to the property store is requested for the first time. You also need to define pairs of ...Get() and ...Set() methods (only ...Get() methods for read-only properties), for instance, TextColorGet() and TextColorSet().

There are two ways to display properties of the particular object in the property grid:

  1. Attach the property store using the CExtPropertyGridCtrl::PropertyStoreSet() method and update the property grid window with the CExtPropertyGridCtrl::PropertyStoreSynchronize() method;
  2. Add a pointer to the property store to the built-in combo box (the CExtPropertyGridComboBoxBar class) and select this item with the SetCurSel() method of the combo box.

The Prof-UIS property grid features displaying and editing properties of more than one object simultaneously. Some properties whose values are different for at least one object are specially marked: for most cell types, the property value cell gets blank but the color selection cell is marked with the question sign inside the gray rectangle. By modifying a property, you modify the same property for all objects whose properties are displayed in the property grid. The operation of combining properties of several objects is performed with the Combine() method of the property store:

  • declare a separate property grid in the same class where the property grid is used;
  • you may need to clear this combined property grid from the previously added property stores by using the ItemRemove() method;
  • use Combine() method as many times as necessary;
  • set the combined property store to the grid with one of the above described ways.

Property grid window

The CExtPropertyGrid class implements a property grid window for displaying property categories and property values. It is base for the CExtPropertyGridCategorized class, which displays categories and values, and for the CExtPropertyGridSorted class, which displays only the property values sorted alphabetically and all the property categories are ignored. In most cases, you do not need to do something special with regard to these classes.

Property grid toolbar

The CExtPropertyGridToolBar class implements the built-in toolbar window in the property grid control. It automatically initializes toolbar buttons which correspond to the tree grid windows created in the property grid control by default, which are controlled by the CExtPropertyGridCategorized and CExtPropertyGridSorted classes.

The property grid toolbar can be hidden or shown with the following code:

CExtPropertyGridToolBar * pWnd =
    STATIC_DOWNCAST(
        CExtPropertyGridToolBar,
        m_PGC.GetChildByRTC(
            RUNTIME_CLASS(CExtPropertyGridToolBar)
            )
        );
if( pWnd == NULL )
    return;
pWnd->ShowWindow( SW_HIDE ); // or SW_SHOW
m_PGC.RecalcLayout(); // obligatory to call this method of the property grid

Property grid combo box

The CExtPropertyGridComboBoxBar class implements the combo box at the top of the property grid control. Each item in the combo box corresponds to a property store. The item text is the display name of the property store. When the selected item is changed, the content of all the grids in the property grid control is reinitialized from the content of the newly selected property store.

The property grid combo box can be hidden or shown with the following code:

CExtPropertyGridComboBoxBar * pWnd =
    STATIC_DOWNCAST(
        CExtPropertyGridComboBoxBar,
        m_PGC.GetChildByRTC(
            RUNTIME_CLASS(CExtPropertyGridComboBoxBar)
            )
        );
if( pWnd == NULL )
    return;
pWnd->ShowWindow( SW_HIDE ); // or SW_SHOW
m_PGC.RecalcLayout(); // obligatory to call this method of the property grid

Tip panel

The CExtPropertyGridTipBar implements the tip panel at the bottom of the property grid control. You set the tip text for the property category and property value with the DescriptionSet() method and the tip text is displayed automatically for the selected category or value automatically.

The property grid combo box can be hidden or shown with the following code:

CExtPropertyGridTipBar * pWnd =
    STATIC_DOWNCAST(
        CExtPropertyGridTipBar,
        m_PGC.GetChildByRTC(
            RUNTIME_CLASS(CExtPropertyGridTipBar)
            )
        );
if( pWnd == NULL )
    return;
pWnd->ShowWindow( SW_HIDE ); // or SW_SHOW
m_PG.RecalcLayout(); // obligatory to call this method of the property grid

Property grid control

The CExtPropertyGridCtrl class is designed to be a container for other windows making up the property grid control (See Figure 2). It automatically detects the CExtPropertyGridWnd windows inside and aligns them in the central part of the container, which is free of other windows. The property grid control assumes all its windows have unique dialog control identifiers. It also handles the WM_COMMAND messages and updates the UI state of the command items with the identifiers equal to the dialog control identifiers of the grid windows inside the container. This makes it possible to hide and show the CExtPropertyGridWnd windows with the CExtPropertyGridToolBar toolbar commands automatically.

Step-by-step instructions on how to use it

Setting up and browsing properties of a single object

For the purpose of simplicity we will create a simple MFC dialog-based application in which the Prof-UIS property grid is used for displaying and editing a few properties of the MFC static control. The explanation will be given in terms of the Visual Studio 2003 IDE.

  1. With the MFC Application Wizard, create a start-up dialog application (Use SimpleProperties as the project name, select Dialog-based as Application type and click Finish.
  2. Add a new CStatic-based class (Select the SimpleProperties project in the Class View window, select Project | Add Class... from the main menu, double click the MFC Class template icon, specify CMyStatic as the class name and CStatic as the base class name, and click Finish).
  3. Add two COLORREF variables and a boolean variable to the protected section of the CMyStatic declaration:
    protected:
        COLORREF m_clrText, m_clrBackground;
        bool m_bIniFont:1;
    and initialize them in the constructor:
    CMyStatic::CMyStatic(): 
    m_clrText( RGB( 255, 173, 91 ) ), 
    m_clrBackground( RGB( 125,158, 192) ),
    m_bIniFont ( true )
    {
    }
  4. Declare and implement ...Get() and ...Set() methods for the two color properties and the font property of the static control. Declare these methods in the public section of the CMyStatic declaration (the MyStatic.h file):
    public:
        COLORREF TextColorGet() const;
        void TextColorSet( COLORREF clr, bool bDefaultColor );
        COLORREF BackgroundColorGet() const;
        void BackgroundColorSet( COLORREF clr, bool bDefaultColor );
        void FontGet( LOGFONT & _lf );
        void FontSet( const LOGFONT & _lf );
    Implement these methods in the MyStatic.cpp file:
    COLORREF CMyStatic::TextColorGet() const
    {
        ASSERT_VALID( this );
        return m_clrText;
    }
    
    void CMyStatic::TextColorSet( COLORREF clr, bool bDefaultColor )
    {
        ASSERT_VALID( this );
        if( m_clrText == clr )
            return;
        m_clrText = clr;
        INT nIndexC = -1, nIndexV = -1;
        (    STATIC_DOWNCAST(
            CExtGridCellColor,
            GetPropertyStore()->
            ItemGetByName( _T("Colors"), 
                nIndexC )->
            ItemGetByName( _T("TextColor"), 
                nIndexV )->
            ValueActiveGet()
            )
            ) -> m_PackedColor.SetColor( clr );
        nIndexC = nIndexV = -1;
        if ( bDefaultColor )
        {
            (    STATIC_DOWNCAST(
                CExtGridCellColor,
                GetPropertyStore()->
                ItemGetByName( _T("Colors"), 
                    nIndexC )->
                ItemGetByName( _T("TextColor"), 
                    nIndexV )->
                ValueDefaultGet()
                )
                ) -> m_PackedColor.SetColor( clr );
        }
        if( GetSafeHwnd() == NULL )
            return;
        Invalidate();
        UpdateWindow();
    }
    COLORREF CMyStatic::BackgroundColorGet() const
    {
        ASSERT_VALID( this );
        return m_clrBackground;
    }
    
    void CMyStatic::BackgroundColorSet( COLORREF clr, bool bDefaultColor )
    {
        ASSERT_VALID( this );
        if( m_clrBackground == clr )
            return;
        m_clrBackground = clr;
    INT nIndexC = -1, nIndexV = -1;
        (    STATIC_DOWNCAST(
                CExtGridCellColor,
                GetPropertyStore()->
                ItemGetByName( _T("Colors"), 
                    nIndexC )->
                ItemGetByName( _T("BackgroundColor"), 
                    nIndexV )->
                ValueActiveGet()
                )
            ) -> m_PackedColor.SetColor( clr );
        if ( bDefaultColor )
        {
            nIndexC = nIndexV = -1;
            (    STATIC_DOWNCAST(
                    CExtGridCellColor,
                    GetPropertyStore()->
                    ItemGetByName( _T("Colors"), 
                        nIndexC )->
                    ItemGetByName( _T("BackgroundColor"), 
                        nIndexV )->
                    ValueDefaultGet()
                    )
                ) -> m_PackedColor.SetColor( clr );
        }
        if( GetSafeHwnd() == NULL )
            return;
        Invalidate();
        UpdateWindow();
    } 
    
    
    void CMyStatic::FontGet( LOGFONT & _lf )
    {
        ASSERT_VALID( this );
        if ( m_bIniFont == false ) 
        {
            (GetFont())->GetLogFont( &_lf );
        }
        else
        {
            (GetFont())->GetLogFont( &_lf );
            _tcscpy( _lf.lfFaceName, _T("Tahoma") );
            _lf.lfWeight = 800;
            {
                CWindowDC dc(0);
                _lf.lfHeight  = 
            - MulDiv( 
                dc.GetDeviceCaps(LOGPIXELSY), 28, 72
                );
            }
            FontSet ( _lf );
        }
    
    }
    
    void CMyStatic::FontSet( const LOGFONT & _lf )
    {
        ASSERT_VALID( this );
    
        CFont font;
        font.CreateFontIndirect( &_lf );
        SetFont(&font);
        font.Detach();
    }
  5. Add a property store variable to the private section of the CMyStatic declaration and initialize it to NULL in the constructor:
    // MyStatic.h
    CExtPropertyStore * m_pPS;
    
    // MyStatic.cpp
    CMyStatic::CMyStatic():
    m_pPS( NULL ), // IT'S IMPORTANT TO INITIALIZE IT TO NULL
    m_clrText( RGB( 255, 173, 91 ) ), 
    m_clrBackground( RGB( 125,158, 192) )
    {
    }
    Do not forget to include the Prof-UIS library in the StdAfx.h file:
    #if (!defined __PROF_UIS_H)
        #include <Prof-UIS.h>
    #endif // (!defined __PROF_UIS_H)
    NOTE: To successfully compile this project, you should compile the Prof-UIS library first (e.g., using the MBCS Debug configuration) and set up paths to the library files in the Visual Studio IDE, which is described in the article Getting Started with Prof-UIS.
  6. Before creating the property store and filling it with categories and values, let us create correspondent classes for property values. It is handy to create a base class (CMyStaticPropertyValueBase) for property value classes, in which the common methods and properties are implemented, for example, keeping a pointer to the object (whose properties are displayed with the property grid, i.e., an instance of CMyStatic) and the property value name:
    // MyStatic.h
    
    class CMyStaticPropertyValueBase : public CExtPropertyValue
    {
    public:
        CExtPropertyGridCtrl m_PGC;
        CMyStatic * m_pStatic;
        DECLARE_SERIAL( CMyStaticPropertyValueBase );
        CMyStaticPropertyValueBase(
            LPCTSTR strPropertyName = NULL,
            CMyStatic * pStatic = NULL
            );
    #ifdef _DEBUG
        virtual void AssertValid() const;
        virtual void Dump( CDumpContext & dc ) const;
    #endif
    };
    
    // MyStatic.cpp
    
    IMPLEMENT_SERIAL( CMyStaticPropertyValueBase, 
                CExtPropertyValue, VERSIONABLE_SCHEMA|1 );
    CMyStaticPropertyValueBase::CMyStaticPropertyValueBase(
        LPCTSTR strPropertyName, // = NULL
        CMyStatic * pStatic // = NULL
        )
        : CExtPropertyValue( strPropertyName )
        , m_pStatic( pStatic )
    {
    #ifdef _DEBUG
        if( m_pStatic != NULL )
        {
            ASSERT_VALID( m_pStatic );
        }
    #endif // _DEBUG
    }
    
    #ifdef _DEBUG
    
    void CMyStaticPropertyValueBase::AssertValid() const
    {
        CExtPropertyValue::AssertValid();
        if( m_pStatic != NULL )
        {
            ASSERT_VALID( m_pStatic );
        }
    }
    void CMyStaticPropertyValueBase::Dump( CDumpContext & dc ) const
    {
        CExtPropertyValue::Dump( dc );
    }
    #endif
    Here are two classes for implementing the text color property value and the background color property value:
    // MyStatic.h
    class CMyStaticProperty_TextColor : public CMyStaticPropertyValueBase
    {
    public:
        DECLARE_SERIAL( CMyStaticProperty_TextColor );
        CMyStaticProperty_TextColor(
            CMyStatic * pStatic = NULL
            );
    #ifdef _DEBUG
        virtual void AssertValid() const;
        virtual void Dump( CDumpContext & dc ) const;
    #endif
        virtual void Apply(
            CExtGridCell * pValue = NULL
            );
    }; 
    
    class CMyStaticProperty_BackgroundColor : 
        public CMyStaticPropertyValueBase
    {
    public:
        DECLARE_SERIAL( CMyStaticProperty_BackgroundColor );
        CMyStaticProperty_BackgroundColor(
            CMyStatic * pStatic = NULL
            );
    #ifdef _DEBUG
        virtual void AssertValid() const;
        virtual void Dump( CDumpContext & dc ) const;
    #endif
        virtual void Apply(
            CExtGridCell * pValue = NULL
            );
    };
    
    // MyStatic.cpp
    IMPLEMENT_SERIAL( CMyStaticProperty_TextColor, 
                CMyStaticPropertyValueBase, 
                VERSIONABLE_SCHEMA|1 );
    CMyStaticProperty_TextColor::CMyStaticProperty_TextColor(
        CMyStatic * m_pStatic // = NULL
        )
        : CMyStaticPropertyValueBase(
        _T("TextColor"),
        m_pStatic
        )
    {
        DescriptionSet( _T("Specifies the text color.") );
        CExtGridCellColor * pValue =
            STATIC_DOWNCAST(
            CExtGridCellColor,
            ValueActiveGetByRTC( RUNTIME_CLASS(CExtGridCellColor) )
            );
        ASSERT_VALID( pValue );
        if( m_pStatic != NULL )
            pValue->m_PackedColor.SetColor(
            m_pStatic->TextColorGet()
            );
        ValueDefaultFromActive();
    }
    
    #ifdef _DEBUG
    
    void CMyStaticProperty_TextColor::AssertValid() const
    {
        CMyStaticPropertyValueBase::AssertValid();
    }
    
    void CMyStaticProperty_TextColor::Dump( CDumpContext & dc ) const
    {
        CMyStaticPropertyValueBase::Dump( dc );
    }
    
    #endif
    
    void CMyStaticProperty_TextColor::Apply(
        CExtGridCell * pValue // = NULL
        )
    {
        ASSERT_VALID( this );
    #ifdef _DEBUG
        if( pValue != NULL )
        {
            ASSERT_VALID( pValue );
            ASSERT_KINDOF( CExtGridCellColor, pValue );
        }
    #endif // _DEBUG
        CMyStaticPropertyValueBase::Apply( pValue );
        if( m_pStatic == NULL )
            return;
        CExtGridCellColor * pValueColor =
            STATIC_DOWNCAST(
            CExtGridCellColor,
            ( ( pValue == NULL ) ? ValueActiveGet() : pValue )
            );
        m_pStatic->TextColorSet(
            pValueColor->m_PackedColor.GetColor(), false
            );
    } // CMyStaticProperty_TextColor
    
    IMPLEMENT_SERIAL( CMyStaticProperty_BackgroundColor, 
                CMyStaticPropertyValueBase, 
                VERSIONABLE_SCHEMA|1 );
    CMyStaticProperty_BackgroundColor::
        CMyStaticProperty_BackgroundColor(
        CMyStatic * m_pStatic // = NULL
        )
        : CMyStaticPropertyValueBase(
        _T("BackgroundColor"),
        m_pStatic
        )
    {
        DescriptionSet( _T("Specifies the background color.") );
        CExtGridCellColor * pValue =
            STATIC_DOWNCAST(
            CExtGridCellColor,
            ValueActiveGetByRTC( RUNTIME_CLASS(CExtGridCellColor) )
            );
        ASSERT_VALID( pValue );
        if( m_pStatic != NULL )
            pValue->m_PackedColor.SetColor(
            m_pStatic->BackgroundColorGet()
            );
        ValueDefaultFromActive();
    }
    
    #ifdef _DEBUG
    
    void CMyStaticProperty_BackgroundColor::AssertValid() const
    {
        CMyStaticPropertyValueBase::AssertValid();
    }
    
    void CMyStaticProperty_BackgroundColor::Dump( CDumpContext & dc ) const
    {
        CMyStaticPropertyValueBase::Dump( dc );
    }
    
    #endif
    
    void CMyStaticProperty_BackgroundColor::Apply(
        CExtGridCell * pValue // = NULL
    )
    {
        ASSERT_VALID( this );
    #ifdef _DEBUG
        if( pValue != NULL )
        {
            ASSERT_VALID( pValue );
            ASSERT_KINDOF( CExtGridCellColor, pValue );
        }
    #endif // _DEBUG
        CMyStaticPropertyValueBase::Apply( pValue );
        if( m_pStatic == NULL )
            return;
        CExtGridCellColor * pValueColor =
            STATIC_DOWNCAST(
            CExtGridCellColor,
            ( ( pValue == NULL ) ? ValueActiveGet() : pValue )
            );
        m_pStatic->BackgroundColorSet(
            pValueColor->m_PackedColor.GetColor(), false
            );
    }
    Here is a class for implementing the text font property value:
    // MyStatic.h
    class CMyStaticProperty_Font : public CMyStaticPropertyValueBase
    {
    public:
        DECLARE_SERIAL( CMyStaticProperty_Font );
        CMyStaticProperty_Font(
            CMyStatic * pStatic = NULL
            );
    #ifdef _DEBUG
        virtual void AssertValid() const;
        virtual void Dump( CDumpContext & dc ) const;
    #endif
        virtual void Apply(
            CExtGridCell * pValue = NULL
            );
    };
    
    // MyStatic.cpp
    IMPLEMENT_SERIAL( CMyStaticProperty_Font, 
                CMyStaticPropertyValueBase, 
                VERSIONABLE_SCHEMA|1 );
    
    CMyStaticProperty_Font::CMyStaticProperty_Font(
        CMyStatic * pStatic // = NULL
        )
        : CMyStaticPropertyValueBase(
        _T("Font"),
        pStatic
        )
    {
        DescriptionSet( _T("Specifies the text font.") );
        CExtGridCellFont * pValue =
            STATIC_DOWNCAST(
            CExtGridCellFont,
            ValueActiveGetByRTC( RUNTIME_CLASS(CExtGridCellFont) )
            );
        ASSERT_VALID( pValue );
        if( m_pStatic != NULL )
        {
            LOGFONT _lf;
            m_pStatic->FontGet( _lf );
            pValue->DataSet( _lf );
        }
        ValueDefaultFromActive();
    }
    
    #ifdef _DEBUG
    
    void CMyStaticProperty_Font::AssertValid() const
    {
        CMyStaticPropertyValueBase::AssertValid();
    }
    
    void CMyStaticProperty_Font::Dump( CDumpContext & dc ) const
    {
        CMyStaticPropertyValueBase::Dump( dc );
    }
    
    #endif
    
    void CMyStaticProperty_Font::Apply(
             CExtGridCell * pValue // = NULL
        )
    {
        ASSERT_VALID( this );
    #ifdef _DEBUG
        if( pValue != NULL )
        {
            ASSERT_VALID( pValue );
            ASSERT_KINDOF( CExtGridCellFont, pValue );
        }
    #endif // _DEBUG
        CMyStaticPropertyValueBase::Apply( pValue );
        if( m_pStatic == NULL )
            return;
        CExtGridCellFont * pValueFont =
            STATIC_DOWNCAST(
            CExtGridCellFont,
            ( ( pValue == NULL ) ? ValueActiveGet() : pValue )
            );
        LOGFONT _lf = pValueFont->DataGet();
        m_pStatic->FontSet( _lf );
    } // CMyStaticProperty_Font
  7. It is convenient to create the property store object when it is requested for the first time. So, here is the declaration and definition of the GetPropertyStore method:
    // MyStatic.h
    public:
    CExtPropertyStore * GetPropertyStore(); 
    
    // MyStatic.cpp
    CExtPropertyStore * CMyStatic::GetPropertyStore()
    {
        ASSERT_VALID( this );
        if( m_pPS != NULL )
            return m_pPS;
    
        m_pPS = new CExtPropertyStore;
    
        CExtPropertyCategory * pCategoryWindow =
            new CExtPropertyCategory( _T("Colors") );
        pCategoryWindow->DescriptionSet( _T("Window colors.") );
        VERIFY( m_pPS->ItemInsert( pCategoryWindow ) );
        pCategoryWindow->ItemInsert(
            new CMyStaticProperty_TextColor( this )
            );
        pCategoryWindow->ItemInsert(
            new CMyStaticProperty_BackgroundColor( this )
            );
        VERIFY( m_pPS->ItemInsert( 
            new CMyStaticProperty_Font( this ) 
            ) 
            );
        
        return m_pPS;
    }
    Since memory for the property store is allocated dynamically, do not forget to delete its pointer in the destructor:
    CMyStatic::~CMyStatic()
    {
        if( m_pPS != NULL ) m_pPS->Delete();
    }
  8. Since some run-time information about CMyStatic will be accessed, make sure that MFC's DECLARE_DYNAMIC and IMPLEMENT_DYNAMIC macros are declared and implemented:
    //MyStatic.h
    public:
        DECLARE_DYNAMIC(CMyStatic)
    
    //MyStatic.cpp
    IMPLEMENT_DYNAMIC(CMyStatic, CStatic)
  9. The last thing we need to add to the CMyStatic class is the WM_CTLCOLOR reflection handler. Right-mouse click the CMyStatic node in the Class window, select Properties, click the Messages button in the toolbar built into the Properties window and select <Add> OnCtlColor next to the =WM_CTLCOLOR message. Fill in the method's body:
    HBRUSH CMyStatic::CtlColor(CDC* pDC, UINT nCtlColor)
    {
    LOGFONT _lf;
        FontGet ( _lf );
        m_bIniFont = false;
        CFont _fnt;
        _fnt.CreateFontIndirect( &_lf );
    
        pDC->SelectObject(&_fnt);
        _fnt.Detach();
        pDC->SetTextColor( m_clrText ); 
        pDC->SetBkMode(TRANSPARENT);
        CBrush br ( m_clrBackground );
        return (HBRUSH)br.Detach();
    }
  10. Open the dialog resource in the Visual Studio editor. You can see the default static control labeled with TODO: Place dialog controls here. Change its id to IDC_STATIC1 and modify its caption to My Text. Adjust its size and location as it is depicted in Figure 5. Declare a variable of the CMyStatic class in the public section of the CSimplePropertiesDlg class:
    public:
        CMyStatic m_wndStatic1;
    Do not forget to include the declaration of this class first:
    // SimplePropertiesDlg.h : header file
    //
    #include "MyStatic.h"
    Add a DDX_Control entry for the m_wndStatic1 to subclass the resource control:
    void CSimplePropertiesDlg::DoDataExchange(CDataExchange* pDX)
    {
        CDialog::DoDataExchange(pDX);
        DDX_Control(pDX, IDC_STATIC1, m_wndStatic1);
    }
  11. Add a Custom control to the dialog resource and change its id to IDC_PROPERTY_GRID_CTRL. Also set its class to ProfUIS-PropertyGridCtrl. Adjust its size and location as it is depicted in Figure 5. Declare a variable of the CExtPropertyGridCtrl type in the public section of the CSimplePropertiesDlg class:
    public:
        CExtPropertyGridCtrl m_PGC;
    Subclass this window with DDX_Control() in the CSimplePropertiesDlg::DoDataExchange() handler:
    DDX_Control(pDX, IDC_PROPERTY_GRID_CTRL, m_PGC);
  12. Somewhere at the end of the CSimplePropertiesDlg::DoDataExchange() handler, add the following code:
    m_wndStatic1.GetPropertyStore()->NameSet(_T("Static1")); 
    CExtPropertyGridComboBoxBar * pCombo =
        STATIC_DOWNCAST(
        CExtPropertyGridComboBoxBar,
        m_PGC.GetChildByRTC(
            RUNTIME_CLASS(CExtPropertyGridComboBoxBar)
            )
        );
    ASSERT_VALID( pCombo );
    pCombo->PropertyStoreInsert( m_wndStatic1.GetPropertyStore() );
    pCombo->SetCurSel(0);
  13. Compile the project and run the application. You should get a picture like in the Figure 5.

    C++/MFC Prof-UIS Property Grid: Default settings for the static control

    Figure 5. Default settings for the static control

Working with multiple objects of the same type

The Prof-UIS property grid allows you to display and edit properties of more than one object simultaneously and it is really easy.

  1. Open the dialog resource in the Visual Studio editor and add one more static control. Adjust its size and position as it is depicted in Figure 6. Change its id to IDC_STATIC2. Declare a variable of the CMyStatic class in the public section of the CSimplePropertiesDlg class:
    public:
        CMyStatic m_wndStatic1, m_wndStatic2;
    Add a DDX_Control entry for the m_wndStatic2 to subclass the resource control:
    void CSimplePropertiesDlg::DoDataExchange(CDataExchange* pDX)
    {
        CDialog::DoDataExchange(pDX);
    DDX_Control(pDX, IDC_STATIC1, m_wndStatic1);
        DDX_Control(pDX, IDC_STATIC2, m_wndStatic2);
    }
  2. Let us change the default colors of the newly added control and leave the default text color intact. Find the below line in CSimplePropertiesDlg::OnInitDialog():
    m_wndStatic1.GetPropertyStore()->NameSet(_T("Static1"));
    and insert the following code after it:
    m_wndStatic2.GetPropertyStore()->NameSet(_T("Static2"));
    m_wndStatic2.BackgroundColorSet( RGB( 255, 173, 91 ), true );
    m_wndStatic2.TextColorSet( RGB( 125,158, 192), true );
  3. Add the property store corresponding to the second static control to the combo box:
    pCombo->PropertyStoreInsert( m_wndStatic2.GetPropertyStore() );
  4. Declare a new property store variable in the public section of the CSimplePropertiesDlg class:
    CExtPropertyStore m_PropertyStoreCompoundSelection;
  5. You can now combine the properties of the two static text controls in this property store:
    m_PropertyStoreCompoundSelection.Combine( 
        m_wndStatic1.GetPropertyStore() 
        );
    m_PropertyStoreCompoundSelection.Combine( 
        m_wndStatic2.GetPropertyStore() 
        );
    m_PropertyStoreCompoundSelection.NameSet( _T("Static1 & Static2") );
    pCombo->PropertyStoreInsert( &m_PropertyStoreCompoundSelection );
  6. Compile the project, run the application, and select Static1 & Static2 in the combo box. The application should look like in the picture below:

    C++/MFC Prof-UIS Property Grid: Properties of both static text controls displayed in the property grid

    Figure 6. Properties of both static text controls displayed in the property grid

    Since the background color and the text color are different for both static text controls, the color icons display the question mark.

Serializing properties to the registry

  1. Declare three methods in the public section of the CSimplePropertiesDialog class:
    // SimplePropertiesDlg.h
    
    public:
    void _Load();
    void _Save();
    CExtSafeString _GetStateRegKeyPath();
    The first two methods load and save the properties from/to the registry and the third one is a helper method:
    // SimplePropertiesDlg.cpp
    
    void CSimplePropertiesDlg::_Load()
    {
        try
        {
            m_PropertyStoreCompoundSelection.ItemRemove();
            CMemFile _file;
            if( ! CExtCmdManager::FileObjFromRegistry(
                    _file,
                    _GetStateRegKeyPath()
                    )
                )
            {
                //ASSERT( FALSE );
                return;
            }
            _file.Seek( 0, CFile::begin );
            CArchive _ar( &_file, CArchive::load );
            m_wndStatic1.GetPropertyStore()->Serialize( _ar );
            m_wndStatic2.GetPropertyStore()->Serialize( _ar );
        }
        catch( CException * pException )
        {
            ASSERT( FALSE );
            pException->Delete();
        }
        catch( ... )
        {
            ASSERT( FALSE );
        }
    }
    
    void CSimplePropertiesDlg::_Save()
    {
        try
        {
            CMemFile _file;
            CArchive _ar( &_file, CArchive::store );
            m_wndStatic1.GetPropertyStore()->Serialize( _ar );
            m_wndStatic2.GetPropertyStore()->Serialize( _ar );
            _ar.Flush();
            _ar.Close();
            _file.Seek( 0, CFile::begin );
            if( ! CExtCmdManager::FileObjToRegistry(
                    _file,
                    _GetStateRegKeyPath()
                    )
                )
            {
                ASSERT( FALSE );
                return;
            }
        }
        catch( CException * pException )
        {
            ASSERT( FALSE );
            pException->Delete();
        }
        catch( ... )
        {
            ASSERT( FALSE );
        }
    }
    
    CExtSafeString CSimplePropertiesDlg::_GetStateRegKeyPath()
    {
        return
            CExtCmdManager::GetSubSystemRegKeyPath(
                _T("Property Grid Control State"),
                _T("Property Grid Control State"),
                _T("Foss"),
                _T("SimpelProperties")
                );
    }
  2. Call the _Load() method somewhere in CSimplePropertiesDlg::OnInitDialog() before you add property stores to the property grid control:
    // SimplePropertiesDlg.cpp
    
    _Load(); // <- Add this line
        CExtPropertyGridComboBoxBar * pCombo =
            STATIC_DOWNCAST(
            CExtPropertyGridComboBoxBar,
            m_PGC.GetChildByRTC(
            RUNTIME_CLASS(CExtPropertyGridComboBoxBar)
            )
            );
        ASSERT_VALID( pCombo );
    Override the OnOK() and OnCancel() methods of the CDialog class and invoke the _Save() method in the overridden versions of these methods:
    // SimplePropertiesDlg.h
    protected:
        virtual void OnOK();
        virtual void OnCancel();
    
    
    // SimplePropertiesDlg.cpp
    
    void CSimplePropertiesDlg::OnOK()
    {
        _Save();
        CDialog::OnOK();
    }
    
    void CSimplePropertiesDlg::OnCancel()
    {
        _Save();
        CDialog::OnCancel();
    }
    The _Save() method saves the current properties of the static text windows (stored in their property stores) to the registry.
  3. At this moment, if you compile and run the application, you will notice that the properties kept in both property stores are loaded and saved successfully from/to registry. By changing properties in the property grid control for any static text control or both, clicking OK and running the application again, you can see that although properties in the property grid control are restored successfully, the appearance of both controls remains default. To renew the control properties, inherit the CSimplePropertiesDlg() class from CExtPropertyItem::IPropertyItemEnumSite class
    CSimplePropertiesDlg : public CDialog
        , public CExtPropertyItem::IPropertyItemEnumSite
    and implement the OnPropertyItemEnum() virtual method:
    // SimplePropertiesDlg.h
    private:
    virtual bool OnPropertyItemEnum(
                CExtPropertyItem * pItem,
                LPVOID pCookie = NULL
            );
    
    // SimplePropertiesDlg.cpp
    bool CSimplePropertiesDlg::OnPropertyItemEnum(
              CExtPropertyItem * pItem,
              LPVOID pCookie // = NULL
     )
    {
        ASSERT_VALID( this );
        ASSERT_VALID( pItem );
        CMyStaticPropertyValueBase * pValue =
            DYNAMIC_DOWNCAST(
            CMyStaticPropertyValueBase,
            pItem
            );
        if( pValue != NULL )
        {
            CMyStatic * pStatic =
                (CMyStatic *)pCookie;
            ASSERT_VALID( pStatic );
            ASSERT_KINDOF( CMyStatic, pStatic );
            pValue->m_pStatic = pStatic;
            pValue->Apply();
        } // if( pValue != NULL )
        return true;
    }
  4. To invoke the OnPropertyItemEnum() method, find these two lines in the CSimplePropertiesDlg::_Load() method
    m_wndStatic1.GetPropertyStore()->Serialize( _ar );
    m_wndStatic2.GetPropertyStore()->Serialize( _ar );
    and add the following two lines right after them:
    m_wndStatic1.GetPropertyStore()->Enum( this, &m_wndStatic1 );
    m_wndStatic2.GetPropertyStore()->Enum( this, &m_wndStatic2 );
  5. Compile and run the application. Change some properties, close the application and run it again. You will see that the static text controls are serialized properly.

FAQ about the Prof-UIS property grid

What is the Prof-UIS property grid?

The Prof-UIS property grid is a set of C++ classes that allows you implement a modern, feature-rich Visual Studio .Net like property grid window for browsing and editing properties of objects of any complexity.

What is the combine operation with regard to the property grid?

The Prof-UIS property grid features displaying and editing properties of more than one object. Each object has its own tree of properties that is described by its property store (the CExtPropertyStore class). You can create a separate instance of CExtPropertyStore and combine properties of several objects in it so that you can subsequently display the combined property store in the property grid:

m_PropertyStoreCompoundSelection.Combine( 
    m_wndStatic1.GetPropertyStore() 
    );
m_PropertyStoreCompoundSelection.Combine( 
    m_wndStatic2.GetPropertyStore() 
    );
m_PropertyGrid.PropertyStoreSet(m_PropertyStoreCompoundSelection );
m_PropertyGrid.PropertyStoreSynchronize();

Is it possible to use the Prof-UIS property grid as a control in a MFC project that does not use other Prof-UIS controls?

Yes, you can use the Prof-UIS property grid as a standalone control.

Keywords

The sample application attached to this article demonstrates the following keywords:

Technology, toolkit, programming language, IDE MFC, C++, Prof-UIS, Visual Studio NET, Visual Studio 6
Graphical User Interface (GUI) GUI sample, dialog-based application, property grid control, displaying and editing properties, categorized view, alphabetic view, combining properties, grid cells
Prof-UIS and MFC classes CDialog, CExtPropertyItem, CExtPropertyValue, CExtGridCell, CExtPropertyValueCompound, CExtPropertyValueMixed, CExtPropertyValueMixedCombined, CExtPropertyCategory, CExtPropertyStore, CExtPropertyGrid, CExtPropertyGridToolBar, CExtPropertyGridComboBoxBar, CExtPropertyGridTipBar, CExtPropertyGridCtrl
Back To Top Other Articles...