PropertyGrid 绑定动态的属性与值的集合

写在前面

如果你对 PropertyGrid 完全不了解,请先阅读 woyouxian 撰写的 用 PropertyGrid 实现 Visual Studio 2005 样式的属性窗口 (Properties Window) 一文,否则阅读本文会有相当大的困难。

由于文章篇幅的关系,本文的代码中省去了所有的注释。你具体写代码的时候,一定要把注释补上,否则过些日子自己可能就看不懂了。

目标

PropertyGrid 基本的用法是绑定一个固定的类,显示这个类的属性和值。

但是有些情况,你需要用到 PropertyGrid 去绑定一个属性/值的集合,但是这个属性/值的集合并不适合写成一个固定的类。

比如你想用 PropertyGrid 绑定XML 里的数据。或者数据库的某个表。

假设你有 1000 个XML 文件,每个 XML 所取到的属性集合各不一样,你不可能为每个XML 文件都写一个类 。

或者你的某个数据表有1000 条记录,该表有 a 字段的值表示属性名称, b字段的值表示属性值,你不可能写一个类,定义1000个属性。

这时候,我们就希望是否能够将一个动态的属性/值的集合与Property 绑定。

该 VB 2005 的实例教程就是解决这样的问题。为了简化问题,我们假设你已经将数据源准备成了一个属性/值的集合 (Collection)。

实例界面

首先我们会设计如下界面。

vb2005

在 Name 和 Value 中输入数据,Name 值表示属性名称,Value 值表示属性的值。点击 Add 按钮后,你输入的信息就会显示在下面的 PropertyGrid 里面;

在 PropertyGrid 中修改一些属性值,点击 Show 以后,会在画面最下面的 TextBox 文本框里看到修改后的值已经存储到用户自定义的属性/值集合 (Collection) 中了。

vb2005

画面中包含控件和属性设置见下表。

对象控件类型属性属性值
XPGridWinFormTextDynamic PropertyGrid - www.BlaBla.cn
  Size475, 540
Label1LabelLocation12, 12
  Size35, 13
  TextName
TxtNameTextBoxLocation53, 9
  Size100, 20
Label2LabelLocation177, 12
  Size34, 13
  TextValue
TxtValueTextBoxLocation217, 9
  Size100, 20
CmdAddButtonLocation380, 7
  Size75, 23
  TextAdd
CmdShowButtonLocation380, 36
  Size75, 23
  TextShow
PGridPropertyGridLocation12, 70
  Size443, 283
TxtShowTextBoxLocation13, 360
  MultilineTrue
  Size443, 134

程序设计

实现这个目标的基本方法是,建立一个属性/值的集合 (Collection) 的类,并将这个类的对象和 PropertyGrid 关联。与此同时,要让 PropertyGrid 不去分析这个类的属性,而使用用户自定义的属性名称和属性值。

System.ComponentModel 中定义了一个接口 ICustomTypeDescriptor,任何 Implements 了这个接口的类和 PropertyGrid 绑定的时候,PropertyGrid 就不去分析这个类的属性,而使用接口中实现的成员来构建属性窗口。

根据以上的分析,基本的程序设计是:

  • 定义一个 XProp 类来保存一对属性名称/值,该类有两个属性,一个是 Name,表示属性名称;另外一个属性是 Value,表示属性值。
  • 再定义一个 XProps 类作为 XProp 的集合 (Collection),并实现 ICustomTypeDescriptor 接口。
  • 另外为了实现需要的接口,还需要定义一个 XPropDescriptor 类把 XProp 中属性名称和属性值对应到 PropertyGrid 上去。XPropDescriptor 类要 Inherits ICustomTypeDescriptor, ICustomTypeDescriptor 是 .Net Framework 提供的一个接口。

代码

建立一个名字叫 XProps.vb 文件,使用Class模板。文件前面包含下面的Imports。

Imports System.ComponentModel
Imports System.Collections.Generic
Imports System.Text

这个文件中将建立下面3个类:

  • XProp
  • XPropDescriptor
  • XProps

建立Class XProp

XProp 就是用户将具体使用的属性。PropertyGrid 很炫的一点就是属性的定制可以有很大的自由度。在这里,给出一种最最简单的形式,属性包括 Name 和 Value 两个部分,在 PropertyGrid 的属性窗口中,Name 将代表左边的属性名称,Value 将代表右边的属性值。

Public Class XProp
	 
Private theName As String = ""
Private theValue As Object = Nothing
	 
	
	Public Property Name() As String
	Get
		Return theName
	End Get
	Set(ByVal value As String)
		theName = value
	End Set
	End Property
		 
	Public Property Value() As Object
	Get
		Return theValue
	End Get
	Set(ByVal value As Object)
		theValue = value
	End Set
	End Property
		 
	Public Overrides Function ToString() As String
		Return "Name: " & Name & ", Value: " & Value
	End Function
	
End Class

这个Class没有特别的地方,对 ToString 的 Overrides 是为了方便后面对整个类的内容显示。

建立Class XPropDescriptor

XPropDescriptior 是建立在 XProp 基础上的,同时是 PropertyDescriptor 的一个派生类。PropertyDescriptor是在 System.ComponentModel 中的,包含了 PropertyGrid 中每个属性所对应的各种接口。XPropDescriptior 就是把 XProp 中的属性和这些接口对应起来。

Public Class XPropDescriptor
Inherits PropertyDescriptor
	 
Private theProp As XProp
	
	Public Sub New(ByVal prop As XProp, ByVal attrs() As Attribute)
		MyBase.New(prop.Name, attrs)
		theProp = prop
	End Sub
	
	Public Overrides Function CanResetValue(ByVal component As Object) As Boolean
		Return False
	End Function
	
	Public Overrides ReadOnly Property ComponentType() As System.Type
	Get
		Return Me.GetType
	End Get
	End Property
	 
	Public Overrides Function GetValue(ByVal component As Object) As Object
		Return theProp.Value
	End Function
		 
	Public Overrides ReadOnly Property IsReadOnly() As Boolean
	Get
		Return False
	End Get
	End Property
		 
	Public Overrides ReadOnly Property PropertyType() As System.Type
	Get
		Return theProp.Value.GetType()
	End Get
	End Property
	
	Public Overrides Sub ResetValue(ByVal component As Object)
		' Do Nothing
	End Sub
		 
	Public Overrides Sub SetValue(ByVal component As Object, ByVal value As Object)
		theProp.Value = value
	End Sub
		 
	Public Overrides Function ShouldSerializeValue(ByVal component As Object) As Boolean
		Return False
	End Function
 
End Class

这一段程序中,大部分都是返回缺省值。需要注意的是下面的部分。

  • New 这个部分把 XProp 的对象导入到类中,存储在变量 theProp 里面,成为将用户属性和 PropertyGrid 使用的属性对应的基础。
  • GetValue 把存储在 XProp 中的属性值放到 PropertyGrid 里面。
  • SetValue 把用户通过画面在 PropertyGrid 中修改了的属性值存储到 XProp 里面。
  • PropertyType 指定这个属性的类型。PropertyGrid 是可以利用不同的类型来选择对应的属性值对话框的。

建立Class XProps

XProps 其实就是 XProp 的 Collection。这很容易,只要用一行代码,继承 System.Collections.Generic.List(Of XProp) 就搞定了。(希望你是了解 Generic 类的,这玩意是 .Net Framework 2.0里面的。)

另一方面,XProps 又需要实现 ICustomTypeDescriptor 的接口,这样把这个类的对象和 PropertyGrid 关联起来的时候,PropertyGrid 就会使用用户自定义的部分来构成属性窗口,而不是用这个类本身的程序Property来构成属性窗口。

Public Class XProps
Inherits List(Of XProp)
Implements ICustomTypeDescriptor
	 
#Region "ICustomTypeDescriptor Implementation"
	
	Public Function GetAttributes() As AttributeCollection _
	Implements ICustomTypeDescriptor.GetAttributes
		Return TypeDescriptor.GetAttributes(Me, True)
	End Function
		 
	Public Function GetClassName() As String _
	Implements ICustomTypeDescriptor.GetClassName
		Return TypeDescriptor.GetClassName(Me, True)
	End Function
	
	Public Function GetComponentName() As String _
	Implements ICustomTypeDescriptor.GetComponentName
		Return TypeDescriptor.GetClassName(Me, True)
	End Function
		 
	Public Function GetConverter() As TypeConverter _
	Implements ICustomTypeDescriptor.GetConverter
		Return TypeDescriptor.GetConverter(Me, True)
	End Function
	
	Public Function GetDefaultEvent() As EventDescriptor _
	Implements ICustomTypeDescriptor.GetDefaultEvent
		Return TypeDescriptor.GetDefaultEvent(Me, True)
	End Function
		 
	Public Function GetDefaultProperty() As PropertyDescriptor _
	Implements ICustomTypeDescriptor.GetDefaultProperty
		Return TypeDescriptor.GetDefaultProperty(Me, True)
	End Function
	
	Public Function GetEditor(ByVal editorBaseType As System.Type) As Object _
	Implements ICustomTypeDescriptor.GetEditor
		Return TypeDescriptor.GetEditor(Me, editorBaseType, True)
	End Function
		 
	Public Function GetEvents() As EventDescriptorCollection _
	Implements ICustomTypeDescriptor.GetEvents
		Return TypeDescriptor.GetEvents(Me, True)
	End Function
	
	Public Function GetEvents(ByVal attributes() As System.Attribute) _
	As EventDescriptorCollection _
	Implements ICustomTypeDescriptor.GetEvents
		Return TypeDescriptor.GetEvents(Me, attributes, True)
	End Function
		 
	Public Function GetProperties() As PropertyDescriptorCollection _
	Implements ICustomTypeDescriptor.GetProperties
		Return TypeDescriptor.GetProperties(Me, True)
	End Function
	
	Public Function GetProperties(ByVal attributes() As System.Attribute) _
	As PropertyDescriptorCollection _
	Implements ICustomTypeDescriptor.GetProperties
		Dim props(Count) As PropertyDescriptor
		Dim i As Int32
		For i = 0 To Count - 1
			props(i) = New XPropDescriptor(Item(i), attributes)
		Next
		Return New PropertyDescriptorCollection(props)
	End Function
		 
	Public Function GetPropertyOwner(ByVal pd As PropertyDescriptor) As Object _
	Implements ICustomTypeDescriptor.GetPropertyOwner
		Return Me
	End Function
	#End Region
		 
	Public Overrides Function ToString() As String
		Dim sbld As StringBuilder = New StringBuilder
		Dim i As Int32
		For i = 0 To Count - 1
			sbld.Append("[" & i & "] " & Item(i).ToString & vbNewLine)
		Next
		Return sbld.ToString
	End Function
	
End Class

这段代码虽然比较冗长,不过读起来并不费力的。

ICustomTypeDescriptor 的实现部分中绝大部分是用 TypeDescriptor中 的成员实现的。只有 GetProperties 是另外写的,这个部分也很简单,就是把 Collection 里面的内容构成一个 PropertyDescriptor 集合,在用这个数组构成 PropertyDescriptorCollection 返回。

需要指出的是,也可以用 Dictionary 或者其他类型的 Collection 来构成这个类,差别部分也就是GetProperties 的写法不同而已。

剩下的部分就是对 ToString 的 Overrides 了,是为了方便后面对整个类的内容显示。这段程序里面使用了 StringBuilder,是属于 System.Text 的。在进行很多字符串组合的时候,这比用运算符 (&) 速度快很多。

界面代码

在 XPGridWin.vb 文件中加入下面的代码。

Public Class XPGridWin
Private XProps As XProps = New XProps
	
	Private Sub XPGridWin_Load(ByVal sender As System.Object, _
	ByVal e As System.EventArgs) Handles MyBase.Load
		PGrid.SelectedObject = XProps
	End Sub
		 
	Private Sub CmdAdd_Click(ByVal sender As System.Object, _
		ByVal e As System.EventArgs) Handles CmdAdd.Click
		Dim xprop As XProp = New XProp
		xprop.Name = TxtName.Text
		xprop.Value = TxtValue.Text
		XProps.Add(xprop)
		PGrid.Refresh()
	End Sub
	
	Private Sub CmdShow_Click(ByVal sender As System.Object, _
		ByVal e As System.EventArgs) Handles CmdShow.Click
		TxtShow.Text = XProps.ToString
	End Sub
	
End Class

这段代码中包含了下面这些内容。

  • 定义一个私有的全局对象 XProps,并在定义的时候创建对象;
  • 在 Form.Load 的时候,把这个对象和 PropertyGrid 关联起来;
  • 在按钮 CmdAdd 点击的时候,创建一个 XProp 对象,赋值 Name 和 Value 后加入到 XProps 中,然后刷新 PropertyGrid;
  • 在按钮 CmdShow 点击的时候,将 XProps 的内容在窗体底部的文字框中显示出来。

测试

在 Name 和 Value 中输入数据,点击 Add 后,这些内容就会加入到 PropertyGrid 里面去;

在 PropertyGrid 中修改一些属性值,点击 Show 以后,会看到修改的值已经存储到用户自定义的Collection 中了。

扩展XProps

在 XProps 上增加几个成员就可以扩展 XProps 的能力。例如增加 LoadFromXml 和 SaveToXml 来实现 Xml 文件到 PropertyGrid 的对应,增加 LoadFromDB 和 SaveToDB 来实现数据库和 PropertyGrid 的对应。这些就不赘述了,相信这些对你不是难题。


编者或作者:    收录日期: 2007-04-28
参考或来源:

上一页: PropertyGrid 控件使用 2 返回上级目录: VB 2005 代码示例 下一页: 生成 MD5 哈希值


© 2008 woyouxian.net 版权所有 Contact Us