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( ) )
{
CMyTreeView* pTreeView= ( get it from somewhere )
CString t;
pTreeView->GetTreeList( t );
propTreeSet[TV_SELECT].SetList( t );
propTreeSet.SetEventCallback( TreeCallback, this );
pPView->ConnectSet( propTreeSet );
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.
#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.