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

Integrated Properties View

0.00/5 (No votes)
9 Mar 2004 1  
An application integrated Properties View

Sample Image - PropertiesView.jpg

Introduction

The properties window in Visual Studio 7 became a new way of triggering events and setting states that the wizard had previously done. I thought it would be nice to have this mechanism in my own code. I've used the MS SwitchSplit example to test this view. It may not be as clear as using a clean project but SwitchSplit comes with a lot going on. It also keeps me from making assumptions where it comes to adding the properties view to an existing application. So far I have four properties connections. One to a dead end set, and one each to the application tree view, list view and form2 view.

The Classes

CPropertiesView

This is just a CView derived class with only one exposed member and one application level event handler and they both do the same thing. The view can be created with or without the CPropertiesDoc. Once you connect the CpropertiesViewSet, the view will maintain itself. You won't have to do anything special with it. The list control has a splitter feature that will maintain the split ratio as it is sized. It will collapse in an orderly fashion when sized vertically. Cursor sets where clicked in editable fields. Keyboard access is mostly functional.
void CPropertiesView::ConnectSet( 
  CPropertiesViewSet& pl, bool activate= true )
Connects the CpropertiesViewSet to the properties view. If you don't want the CpropertiesViewSet you are connecting to become the active set, activate should be false. This can be called repeatedly without harm.

The alternative is to use:

AfxGetMainWnd( )->SendMessageToDescendants( 
  ID_REG_PVM_CONNECT, (WPARAM)&thePropSet );
This is a registered message so you don't have to worry about conflicts. By messaging, you don't have to have a pointer to the properties view. LParam is optional. Because it defaults to NULL, you would set it true if you did not want to activate the properties set and only connect it.

CPropertiesViewSet

This is the class you will be working with. You create a CpropertiesViewSet object for each of the application's set of properties you would like to expose in the properties view and connect it to the view. The connection mechanism is self-contained. You won't break stuff if the view is destroyed before the properties set. If you destroy a set while it is active in the view, the view will just clear. As of now CpropertiesViewSet is not derived from CObject, so there are no asserts on kindof in the code.

Constructor

CpropertiesViewSet( long nId= 0, long nGrp= 0 );

You have the option of setting the ID and Group number on construction or later. The ID is used on your side of the connection so you can distinguish the CpropertiesViewSet objects in the event callback.

The group number is used if you have multiple CpropertiesViewSet objects you would like to appear in the dropdown title of the view. If set to zero, the set will remain orphaned from all the other sets. This is the next best thing to a tool bar for switching between sets in the properties view. The tree view and the list view share the dropdown title in the example.

The Event Connection

I have ended up using callbacks for the event connection. The connection described above only connects the CpropertiesViewSet with the view. But to be useful, the object that owns the CpropertiesViewSet needs to be notified when a change is taking place in the view. This is the event connection. This connection is not required and there may be times when you don't need it. But, if for instance, you use the evUser or evUserNE types described below, they won't do much without this connection. Without this connection you can update the view with ReloadProperties( ) and any changes in the view are immediately available in CpropertiesViewSet. To make the event connection, the SetEventCallback is used. You will need a global or static member with a prototype of:

bool CALLBACK function( LPVOID owner, int action, int item, int id )
  • owner is the currently connected owner object.
  • action button pushed, edit changed, etc..
  • item is the index into the list of property items.
  • id The ID of the CpropertiesViewSet connected, optional use.
  • return True if the change is accepted. You can also change the item before returning and that change will be reflected in the view.

This connection only needs to be made once, but can be repeated without harm. You can have several of one type of object independently sharing the properties view with one static function. It is your responsibility that your parent object, (owner), persists as long as connection does. So, either make CpropertiesViewSet a member of the object and it will handle itself on destruction, or, call ClearEventCallback from the destructor of your parent object, or, do a SetEventCallback to some new object before the old object is destroyed. There are several possible ways to set up the application end of the connection. If you have one parent object with several CPropertiesViewSet objects, set the ID in the CPropertiesViewSet so you can distinguish the CpropertiesViewSet object in the callback. That, or use different callbacks for each CpropertiesViewSet.

When ever you need to update changes in this set and/or want to make this the active set in the properties view, call ReloadProperties( ).

This class is the container for the CPropertiesViewItem objects.

Members

void ReloadProperties( )
Makes this the active object in the properties view and show the current state of the property set.
void AppendSet( PROPERTIESVIEW_ITEM& item ) 
Adds a CPropertiesViewItem to this container.
void ClearSet( ) 
Removes all the CPropertiesViewItem(s) from this container and clears the view if the connection is active.
PROPERTIESVIEW_ITEM& GetItem( long item )
PROPERTIESVIEW_ITEM& operator [] ( long item ) 
Returns the indexed item in this container.
void SetTitle( LPCTSTR t ) 
Sets the title used in the properties view. Use a \n delimited string to make bold then normal parts of the title. "This is bold\nnormal text\n". This is optional, you can set the title with a normal string like, "This Title" and it will be all bold.
void SetID( long nId ) 
Sets the ID value of this object. (Optional)
long GetID( ) 
Get the ID of this object.
void SetGroup( long nGrp ) 
Sets the Group value of this object. (Optional)
long GetGroup( )
Get the Group number of this object.
void SetEventCallback(  PVEVENTCALLBACK event, LPVOID object ) 
Used to connect your parent object, (like a view or document), to the event trigger of the view. Your object is notified when an item has been changed or if the evUser, evUserNE type is set in the item, that the user has alt/arrow_down or mouse clicked the item button.
bool IsConnected( )
Returns true if this has been connected to the view. There is no check for the event callback, as that should be understood.
void ClearEventCallback( ) 
Disconnects the callback from your object. Used internally and you will likely never use it. It is exposed for the case you need it.

CPropertiesViewItem

Append one of these to the CPropertiesViewSet for each entry in the properties view. The view item styles are set from the enum Type found in the header. The dropdown list is currently set with a \n delimited string. Methods can be added for other types of lists. I have not added common dialogs to the view because it makes more sense to handle this kind of stuff on the application side. To add common dialogs would mean a lot of extra baggage that may never get used, especially because of the specialization of dialogs. The evUser and evUserNE types will display a (...) button and you can do whatever you like in the callback. This is demonstrated in the tree view properties example. NE types are 'Not Editable'. If needed, you can change the type at any time.

Members

void SetType( Type t )

Sets the type of this item.

  • evBool Show a true/false dropdown. Set/retreave with SetIndex/GetIndex
  • evEdit Text editable field.
  • evDropDown Dropdown selection
  • evUser (...) Button displayed and an editable field.
  • evUserNE(...) Button displayed and a non-editable field.
SetLabel( LPCTSTR newLable )
Sets the label that is used in the left column of the properties view.
void SetDescription( LPCTSTR newDescription )
Sets the text shown in the bottom of the properties view when the item is selected.
SetList( LPCTSTR inList )
If this item is type evDropDown, use this to set the list. The list is delimited with \n such as: "First\nSecond\nThird\n".
void SetIndex( long i )
If this is a dropdown item, use this member to set the index of the selection. The default is the first item of the list.
SetStr( LPCTSTR edit )
This sets the item text that is displayed in the right column of the properties view. The display is overridden in the case of a dropdown with items.
long GetIndex( )
Return the current selection index of a dropdown item.
LPCTSTR GetStr( )
Gets the string of the items right column value.

The Header Files

You will only need the PropertiesView.h where you create the properties view. I�ve split off the implementation of the CpropertiesViewSet and put it into the PropertiesSet.h header. In the example, if I had not used the ConnectSet member of the view, and only used messaging to connect, I would have not needed the PropertiesView.h.

The Example Project

I'll explain the tree view connection here. There are more examples in the project. The tree view properties are all done in the document while the list view is handled in the view. I did it both ways to see what I would run into. There is a CpropertiesViewSet member in the doc called propTreeSet. In OnOpenDocument the set is initialized with:

    propTreeSet.SetTitle( _T("TreeView Properties") );

    PROPERTIESVIEW_ITEM item;
    item.SetType( CPropertiesViewItem::evUserNE );
    item.SetLabel( _T("Background Color") );
    item.SetDescription( _T("Set the Background color of the Tree View") );
    CString t;
    t.Format( _T("0x%8.8x"), colorTreeBkgd );
    t.MakeUpper( );
    item.SetStr( t );
    propTreeSet.AppendSet( item );

    item.SetType( CPropertiesViewItem::evDropDown );
    item.SetLabel( _T("Selection") );
    item.SetDescription( _T("Set the selection of the tree") );
    item.SetStr( _T("(empty set)") );
    propTreeSet.AppendSet( item );

I'll be adding a member to the CpropertiesViewSet to make it xml aware. This means you can initialize the set with a call like:

 propTreeSet.LoadXML( anXMLNode )
There is a Doc member that handles an event when the tree gets focus. So at that time I know the view is good and the first time called I can make both connections.
void CSDIViewSwitchDoc::OnTreeViewFocus( )
{
    if( bLockTest )
        return;

    CPropertiesView* pPView= GetCPView( );
    if( pPView == NULL )
        return;

    if( !propTreeSet.IsConnected( ) )
    {
        //now there is a tree view so load the selection list

        CMyTreeView* pTreeView= ( get it from somewhere )
        CString t;
        pTreeView->GetTreeList( t );
        propTreeSet[TV_SELECT].SetList( t );
        propTreeSet.SetEventCallback( TreeCallback, this );
        pPView->ConnectSet( propTreeSet );
        //You don't need a pointer to the Properties View

        //Send this message instead

        AfxGetMainWnd( )->SendMessageToDescendants(
            ID_REG_PVM_CONNECT, (WPARAM)& propTreeSet )
    }
    else
        propTreeSet.ReloadProperies( );
}

This would be typical if you don�t have values before runtime. The list of views is taken from the tree view to populate the dropdown. You can do SetEventCallback and ConnectSet in either order. Once connected, the ReloadProperties member will make this set active in the view.

The event callback, in this case, was made a global instead of a doc member. It works the same either way.

//These corospond to the index of the items in the CPropertiesViewSet

#define TV_SETCOLOR 0
#define TV_SELECT 1

bool CALLBACK TreeCallback( LPVOID object, int action, int item, int id )
{
    CSDIViewSwitchDoc* pDoc= static_cast<CSDIViewSwitchDoc*>( object );
    CMyTreeView* pTreeView= ( get it from somewhere );
    if( pTreeView == NULL )
        return false;

    switch( item )
    {
    case TV_SETCOLOR:
    {
        CColorDialog dlg( pDoc->colorTreeBkgd );
        if( dlg.DoModal( ) == IDOK )
        {
            CString t;
            pDoc->colorTreeBkgd= dlg.GetColor( );
            t.Format( _T("0x%8.8x"), pDoc->colorTreeBkgd );
            t.MakeUpper( );
            pDoc->propTreeSet[TV_SETCOLOR].SetStr( t );
            pTreeView->SendMessage( TVM_SETBKCOLOR, 0, pDoc->colorTreeBkgd );
            return true;
        }
        return false;
    }
    case TV_SELECT:
        pTreeView->SetSelected( pDoc->propTreeSet[TV_SELECT].GetIndex( ) );
        return true;
    }
    ASSERT( false );
    return false;
}

Things to do, Next version

  • Figure out what to do with the document.
  • Allow a tool bar to be set in view.
  • Add XML members to the CPropertiesViewSet.

Conclusion

Any enhancements done from here should not break old implementations. If you test the Unicode build, you will get 'data corruption' errors as SwitchSplit wasn�t written for Unicode. But it will run and I've tested it far enough that this system should be fine in Unicode. I've made a lot of hacks to the SwitchSplit application to test this out but it looks like it won't break. The update for the slider in Form2 works both ways. Feedback will be most welcome.

Thanks, Dan.

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