PropertyGrid 基本的用法是绑定一个固定的类,显示这个类的属性和值。
但是有些情况,你需要用到 PropertyGrid 去绑定一个属性/值的集合,但是这个属性/值的集合并不适合写成一个固定的类。
比如你想用 PropertyGrid 绑定XML 里的数据。或者数据库的某个表。
假设你有 1000 个XML 文件,每个 XML 所取到的属性集合各不一样,你不可能为每个XML 文件都写一个类 。
或者你的某个数据表有1000 条记录,该表有 a 字段的值表示属性名称, b字段的值表示属性值,你不可能写一个类,定义1000个属性。
这时候,我们就希望是否能够将一个动态的属性/值的集合与Property 绑定。
该 VB 2005 的实例教程就是解决这样的问题。为了简化问题,我们假设你已经将数据源准备成了一个属性/值的集合 (Collection)。
首先我们会设计如下界面。
在 Name 和 Value 中输入数据,Name 值表示属性名称,Value 值表示属性的值。点击 Add 按钮后,你输入的信息就会显示在下面的 PropertyGrid 里面;
在 PropertyGrid 中修改一些属性值,点击 Show 以后,会在画面最下面的 TextBox 文本框里看到修改后的值已经存储到用户自定义的属性/值集合 (Collection) 中了。
画面中包含控件和属性设置见下表。
对象 | 控件类型 | 属性 | 属性值 |
XPGridWin | Form | Text | Dynamic PropertyGrid - book.chinaz.com/html |
Size | 475, 540 | ||
Label1 | Label | Location | 12, 12 |
Size | 35, 13 | ||
Text | Name | ||
TxtName | TextBox | Location | 53, 9 |
Size | 100, 20 | ||
Label2 | Label | Location | 177, 12 |
Size | 34, 13 | ||
Text | Value | ||
TxtValue | TextBox | Location | 217, 9 |
Size | 100, 20 | ||
CmdAdd | Button | Location | 380, 7 |
Size | 75, 23 | ||
Text | Add | ||
CmdShow | Button | Location | 380, 36 |
Size | 75, 23 | ||
Text | Show | ||
PGrid | PropertyGrid | Location | 12, 70 |
Size | 443, 283 | ||
TxtShow | TextBox | Location | 13, 360 |
Multiline | True | ||
Size | 443, 134 |
实现这个目标的基本方法是,建立一个属性/值的集合 (Collection) 的类,并将这个类的对象和 PropertyGrid 关联。与此同时,要让 PropertyGrid 不去分析这个类的属性,而使用用户自定义的属性名称和属性值。
System.ComponentModel 中定义了一个接口 ICustomTypeDescriptor,任何 Implements 了这个接口的类和 PropertyGrid 绑定的时候,PropertyGrid 就不去分析这个类的属性,而使用接口中实现的成员来构建属性窗口。
根据以上的分析,基本的程序设计是:
建立一个名字叫 XProps.vb 文件,使用Class模板。文件前面包含下面的Imports。
Imports System.ComponentModel
Imports System.Collections.Generic
Imports System.Text
这个文件中将建立下面3个类:
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 是为了方便后面对整个类的内容显示。
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
这一段程序中,大部分都是返回缺省值。需要注意的是下面的部分。
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 上增加几个成员就可以扩展 XProps 的能力。例如增加 LoadFromXml 和 SaveToXml 来实现 Xml 文件到 PropertyGrid 的对应,增加 LoadFromDB 和 SaveToDB 来实现数据库和 PropertyGrid 的对应。这些就不赘述了,相信这些对你不是难题。