集成属性视图






2.44/5 (6投票s)
应用程序集成属性视图
引言
Visual Studio 7 中的属性窗口成为触发事件和设置向导之前已完成的状态的新方法。我认为最好能在我的代码中拥有这个机制。我已经使用了 MS SwitchSplit 示例来测试此视图。它可能不如使用干净的项目那样清晰,但 SwitchSplit 具有很多功能。它还能阻止我在现有应用程序中添加属性视图时做出假设。到目前为止,我有四个属性连接。一个连接到死胡同集,一个连接到应用程序树视图、列表视图和 form2 视图。类
CPropertiesView
这只是一个 CView
派生类,只有一个公开的成员和一个应用程序级别的事件处理程序,它们都执行相同的操作。该视图可以创建,也可以不创建 CPropertiesDoc
。一旦连接了 CpropertiesViewSet
,视图将自行维护。您无需对其进行任何特殊操作。列表控件具有一个分割器功能,可以保持分割比例不变。光标设置在可编辑字段中单击。键盘访问功能基本正常。void CPropertiesView::ConnectSet( CPropertiesViewSet& pl, bool activate= true )将
CpropertiesViewSet
连接到属性视图。如果您不想将您连接的 CpropertiesViewSet
成为活动集,则 activate
应为 false
。可以重复调用此函数而不会造成损害。另一种选择是使用
AfxGetMainWnd( )->SendMessageToDescendants( ID_REG_PVM_CONNECT, (WPARAM)&thePropSet );这是一个已注册的消息,因此您不必担心冲突。通过消息传递,您不必拥有属性视图的指针。LParam 是可选的。因为它默认为
NULL
,所以如果您不希望激活属性集而只进行连接,则将其设置为 true。CPropertiesViewSet
这是您将要使用的类。您为希望在属性视图中公开的每个应用程序属性集创建一个 CpropertiesViewSet
对象,并将其连接到视图。连接机制是自包含的。如果视图在属性集销毁之前被销毁,您也不会弄乱。如果您在视图中销毁了一个集,视图将只会被清除。目前 CpropertiesViewSet
不派生自 CObject
,因此代码中没有 kindof 的断言。
构造函数
CpropertiesViewSet( long nId= 0, long nGrp= 0 );
您可以在构造时或稍后设置 ID 和组号。ID 在连接的您这边使用,以便您可以在事件回调中区分 CpropertiesViewSet
对象。
组号用于当您有多个 CpropertiesViewSet
对象希望它们出现在视图的下拉标题中时。如果设置为零,则该集将与其他所有集分离。这是在属性视图中在各个集之间切换的工具栏的次优选择。树视图和列表视图在示例中共享下拉标题。
事件连接
我最终使用了回调来进行事件连接。上面描述的连接仅将 CpropertiesViewSet
与视图连接起来。但是,要使对象有用,拥有 CpropertiesViewSet
的对象需要在视图中发生更改时得到通知。这就是事件连接。此连接不是必需的,有时您可能不需要它。但是,例如,如果您使用下面描述的 evUser 或 evUserNE
类型,则没有此连接它们将无法正常工作。没有此连接,您可以通过 ReloadProperties()
更新视图,并且视图中的任何更改都会立即在 CpropertiesViewSet
中可用。要进行事件连接,请使用 SetEventCallback
。您将需要一个具有原型的全局或静态成员
bool CALLBACK function( LPVOID owner, int action, int item, int id )
- owner 是当前连接的所有者对象。
- action 按下按钮,编辑更改等。
- item 是属性项列表中的索引。
- id 连接的
CpropertiesViewSet
的 ID,可选使用。 - return 如果更改被接受,则返回 True。您也可以在返回之前更改该项,并且该更改将反映在视图中。
此连接只需要连接一次,但可以重复连接而不会造成损害。您可以让一个静态函数独立共享属性视图的同一种对象。您的父对象(所有者)的生存期必须与连接的生存期一样长。因此,要么将 CpropertiesViewSet
设为您对象的成员,它会在销毁时自行处理,要么在父对象的析构函数中调用 ClearEventCallback
,要么在旧对象销毁之前将 SetEventCallback
设置为某个新对象。有几种可能的方法来设置连接的应用程序端。如果您有一个父对象有多个 CPropertiesViewSet
对象,请在 CPropertiesViewSet
中设置 ID,以便您可以在回调中区分 CPropertiesViewSet
对象。或者,为每个 CPropertiesViewSet
使用不同的回调。
每当您需要更新此集中的更改和/或希望将其设置为属性视图中的活动集时,请调用 ReloadProperties()
。
此类是 CPropertiesViewItem
对象的容器。
成员
void ReloadProperties( )
将此设为属性视图中的活动对象,并显示属性集的当前状态。void AppendSet( PROPERTIESVIEW_ITEM& item )
将 CPropertiesViewItem
添加到此容器。void ClearSet( )
从此容器中移除所有 CPropertiesViewItem
并清除视图(如果连接已激活)。PROPERTIESVIEW_ITEM& GetItem( long item ) PROPERTIESVIEW_ITEM& operator [] ( long item )返回此容器中的索引项。
void SetTitle( LPCTSTR t )
设置属性视图中使用的标题。使用 \n 分隔的字符串来创建标题的粗体部分和正常部分。“This is bold\nnormal text\n”。这是可选的,您可以使用普通字符串设置标题,例如“This Title”,它将全部为粗体。void SetID( long nId )设置此对象的 ID 值。(可选)
long GetID( )
获取此对象的 ID。void SetGroup( long nGrp )设置此对象的 Group 值。(可选)
long GetGroup( )
获取此对象的组号。void SetEventCallback( PVEVENTCALLBACK event, LPVOID object )
用于将您的父对象(例如视图或文档)连接到视图的事件触发器。当项被更改时,您的对象会收到通知,或者如果项中设置了 evUser、evUserNE 类型,则用户已按 alt/arrow_down 或鼠标单击了项按钮。bool IsConnected( )
如果已连接到视图,则返回 true。没有对事件回调的检查,因为应该可以理解。void ClearEventCallback( )
断开您的对象与回调的连接。内部使用,您可能永远不会使用它。它暴露出来是为了以防万一您需要它。CPropertiesViewItem
将这些对象之一附加到 CPropertiesViewSet
,对应于属性视图中的每个条目。视图项样式从头文件中的 Type 枚举设置。下拉列表当前使用 \n 分隔的字符串设置。可以为其他类型的列表添加方法。我没有向视图添加通用对话框,因为在应用程序端处理这类事情更有意义。添加通用对话框将意味着很多额外的开销,这些开销可能永远不会被使用,特别是由于对话框的特殊性。evUser
和 evUserNE
类型将显示一个 (...) 按钮,您可以在回调中执行任何您喜欢的操作。树视图属性示例中对此进行了演示。NE 类型是“不可编辑”。如果需要,您可以随时更改类型。
成员
void SetType( Type t )
设置此项的类型。
- evBool 显示一个真/假下拉列表。使用 SetIndex/GetIndex 设置/检索
- evEdit 可编辑文本字段。
- evDropDown 下拉选择
- evUser (...) 按钮显示和一个可编辑字段。
- evUserNE (...) 按钮显示和一个不可编辑字段。
SetLabel( LPCTSTR newLable )设置在属性视图左侧列使用的标签。
void SetDescription( LPCTSTR newDescription )
设置当选中该项时在属性视图底部显示的文本。SetList( LPCTSTR inList )如果此项的类型是
evDropDown
,则使用此项设置列表。列表以 \n 分隔,例如:“First\nSecond\nThird\n”。void SetIndex( long i )如果是下拉项,请使用此成员设置选择的索引。默认值为列表中的第一项。
SetStr( LPCTSTR edit )这会设置在属性视图右侧显示的项文本。如果该项是具有项目的下拉列表,则显示将被覆盖。
long GetIndex( )
返回下拉项的当前选择索引。LPCTSTR GetStr( )获取右侧列值的字符串。
头文件
您只需要 PropertiesView.h 来创建属性视图。我已经将CpropertiesViewSet
的实现拆分并放入了 PropertiesSet.h 头文件中。在示例中,如果我没有使用视图的 ConnectSet
成员,而只使用消息传递进行连接,那么我将不需要 PropertiesView.h。示例项目
我将在此解释树视图连接。项目中还有更多示例。树视图属性全部在文档中完成,而列表视图则在视图中处理。我通过这两种方式进行了尝试,看看会遇到什么。文档中有一个名为propTreeSet
的 CpropertiesViewSet
成员。在 OnOpenDocument
中,该集使用以下方式初始化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 );
我将为 CPropertiesViewSet
添加一个成员,使其能够识别 XML。这意味着您可以通过类似以下方式的调用来初始化该集
propTreeSet.LoadXML( anXMLNode )有一个 Doc 成员,它处理树获得焦点时的事件。因此,那时我知道视图是好的,并且第一次调用时,我可以进行两次连接。
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( ); }
如果您在运行时没有值,这通常是这样做的。视图列表是从树视图中获取的,以填充下拉列表。您可以按任意顺序执行 SetEventCallback
和 ConnectSet
。连接后,ReloadProperties
成员将使此集在视图中处于活动状态。
在这种情况下,事件回调被设为全局变量而不是文档成员。它以任何一种方式工作。
//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; }
待办事项,下一版本
- 弄清楚如何处理文档。
- 允许在视图中设置工具栏。
- 向
CPropertiesViewSet
添加 XML 成员。
结论
从此进行的任何增强都不应破坏旧的实现。如果您测试 Unicode 构建,您将收到“数据损坏”错误,因为 SwitchSplit
不是为 Unicode 编写的。但是它会运行,并且我已经测试了足够长的时间,以至于该系统在 Unicode 中应该没问题。我修改了 SwitchSplit
应用程序以进行测试,但这看起来不会有问题。Form2 中滑块的更新在两个方向上都有效。非常欢迎反馈。
谢谢,Dan。