Author:水如烟
自联表我们经常用到,它总是跟树联结在一起。
对于它们的处理,.NET没有专门的处理类。
控件类TreeNode,也没有直接跟自联表挂上钩。
所以,我也尝试一下写写这方面的代码。
如我以往所写的一样,仅提供一种方法,至于更好的方法,更好的效率,鉴于自己学识所限,不深究。
通常的,要做成泛型类才能通用。所以,若还是使用.Net FrameWork1.1的话,无法使用下面的类了。
下面是为应用自联表做的树类,只有两个文件:
Node.vb
Namespace LzmTW.uSystem.uCollection
<Serializable()> _
Public Class Node(Of T)
Friend gIsRoot As Boolean = True
Friend gParent As Node(Of T)
''' <summary>
''' 当前节点的父节点
''' </summary>
Public ReadOnly Property Parent() As Node(Of T)
Get
If Me.IsRoot Then
Return Nothing
End If
Return gParent
End Get
End Property
''' <summary>
''' 树的深度
''' </summary>
Public ReadOnly Property Level() As Integer
Get
If Me.IsRoot Then
Return 0
End If
Return Me.Parent.Level + 1
End Get
End Property
''' <summary>
''' 当前节点是否是根节点
''' </summary>
Public ReadOnly Property IsRoot() As Boolean
Get
Return gIsRoot
End Get
End Property
Private gUserData As Object
''' <summary>
''' 获取或设置包含树节点有关数据的对象
''' </summary>
Public Property Tag() As Object
Get
Return gUserData
End Get
Set(ByVal value As Object)
gUserData = value
End Set
End Property
Private gItem As T
Public Property Item() As T
Get
Return gItem
End Get
Set(ByVal value As T)
gItem = value
End Set
End Property
Friend gChildren As NodeCollection(Of T)
''' <summary>
''' 获取第一个子树节点
''' </summary>
Public ReadOnly Property FirstNode() As Node(Of T)
Get
If gChildren.Count = 0 Then
Return Nothing
End If
Return gChildren(0)
End Get
End Property
''' <summary>
''' 获取最后一个子树节点
''' </summary>
Public ReadOnly Property LastNode() As Node(Of T)
Get
If gChildren.Count = 0 Then
Return Nothing
End If
Return gChildren(gChildren.Count - 1)
End Get
End Property
Private gNodes As NodeCollection(Of T)
''' <summary>
''' 当前节点的节点集合
''' </summary>
Public ReadOnly Property Nodes() As NodeCollection(Of T)
Get
Return gNodes
End Get
End Property
''' <summary>
''' 当前节点在节点集合中的位置
''' </summary>
Public ReadOnly Property Index() As Integer
Get
Return GetIndex()
End Get
End Property
Private Function GetIndex() As Integer
If Me.IsRoot Then
Return 0
End If
Return Me.Parent.Nodes.IndexOf(Me)
End Function
''' <summary>
''' 获取下一个同级树节点
''' </summary>
Public ReadOnly Property NextNode() As Node(Of T)
Get
If Me.IsRoot OrElse Me.Index + 1 > Me.Parent.Nodes.Count Then
Return Nothing
End If
Return Me.Parent.Nodes.Item(Me.Index + 1)
End Get
End Property
''' <summary>
''' 获取上一个同级树节点
''' </summary>
Public ReadOnly Property PrevNode() As Node(Of T)
Get
If Me.IsRoot OrElse Me.Index - 1 < 0 Then
Return Nothing
End If
Return Me.Parent.Nodes.Item(Me.Index - 1)
End Get
End Property
Private Sub Initialzie()
gNodes = New NodeCollection(Of T)(Me)
gChildren = New NodeCollection(Of T)(Me)
gByProperty = Not uSystem.uReflection.CommonFunction.TypeHasFields(GetType(T))
End Sub
Sub New()
Initialzie()
End Sub
Sub New(ByVal item As T)
gItem = item
Initialzie()
End Sub
Public Function GetNodeCount(ByVal includeSubNodes As Boolean) As Integer
Dim mCount As Integer = gChildren.Count
If includeSubNodes Then
Dim mIndex As Integer = 0
Do While mIndex < gChildren.Count
mCount += gChildren(mIndex).GetNodeCount(True)
mIndex += 1
Loop
End If
Return mCount
End Function
Public Sub Remove()
If Me.IsRoot Then
Throw New Exception("不能移除根节点")
End If
Me.Parent.Nodes.RemoveAt(Me.Index)
End Sub
Private gTable As DataTable
Private gByProperty As Boolean
''' <summary>
''' 将当前节点树转换为表
''' </summary>
''' <param name="includeSubNodes">是否包括子节点的T对象</param>
Public Function ConvertToDataTable(ByVal includeSubNodes As Boolean) As DataTable
gTable = uSystem.uReflection.CommonFunction.CreateTableFromType(GetType(T))
If gTable.Columns.Count = 0 Then
If gByProperty Then
Throw New Exception("对象无属性列")
Else
Throw New Exception("对象无字段列")
End If
End If
Me.ForEach(New Action(Of T)(AddressOf GetDataTableDatasAction), includeSubNodes)
gTable.AcceptChanges()
Return gTable
End Function
Private Sub GetDataTableDatasAction(ByVal item As T)
uSystem.uReflection.CommonFunction.ItemAppendToTable(Of T)(item, gTable)
End Sub
''' <summary>
''' 将当前节点树转换为TreeNode
''' </summary>
''' <param name="NameOfTreeNodeText">TreeNode的Text值对应的T对象属性名或字段名</param>
''' <param name="includeSubNodes">是否包括子节点</param>
''' <remarks>TreeNode的Tag存T对象值</remarks>
Public Function ConvertToTreeNode(ByVal nameOfTreeNodeText As String, ByVal includeSubNodes As Boolean) As Windows.Forms.TreeNode
CheckValid(gByProperty, nameOfTreeNodeText)
Dim mTreeNode As System.Windows.Forms.TreeNode = ConvertToTreeNode(Me, gByProperty, nameOfTreeNodeText)
If includeSubNodes Then AppendTreeNode(mTreeNode, Me, gByProperty, nameOfTreeNodeText)
Return mTreeNode
End Function
Private Shared Sub AppendTreeNode(ByVal treeNode As Windows.Forms.TreeNode, ByVal node As Node(Of T), ByVal byProperty As Boolean, ByVal nameOfTreeNodeText As String)
For Each n As Node(Of T) In node.gChildren
Dim mCurrentTreeNode As Windows.Forms.TreeNode = ConvertToTreeNode(n, byProperty, nameOfTreeNodeText)
treeNode.Nodes.Add(mCurrentTreeNode)
AppendTreeNode(mCurrentTreeNode, n, byProperty, nameOfTreeNodeText)
Next
End Sub
Private Shared Function ConvertToTreeNode(ByVal node As Node(Of T), ByVal byProperty As Boolean, ByVal nameOfTreeNodeText As String) As System.Windows.Forms.TreeNode
Dim mTextValue As Object
If byProperty Then
mTextValue = GetType(T).GetProperty(nameOfTreeNodeText).GetValue(node.Item, Nothing)
Else
mTextValue = GetType(T).GetField(nameOfTreeNodeText).GetValue(node.Item)
End If
If mTextValue Is Nothing Then
mTextValue = "Root"
End If
Dim mTreeNode As New System.Windows.Forms.TreeNode(mTextValue.ToString)
mTreeNode.Tag = node.Item
Return mTreeNode
End Function
Private Sub CheckValid(ByVal byProperty As Boolean, ByVal nameOfTreeNodeText As String)
If byProperty Then
Dim mPropertyInfo As System.Reflection.PropertyInfo = GetType(T).GetProperty(nameOfTreeNodeText)
If mPropertyInfo Is Nothing Then
Throw New Exception("属性名无效")
If Not mPropertyInfo.CanRead Then
Throw New Exception("属性名不可读")
End If
End If
Else
Dim mFieldInfo As System.Reflection.FieldInfo = GetType(T).GetField(nameOfTreeNodeText)
If mFieldInfo Is Nothing Then
Throw New Exception("字段名无效")
End If
End If
End Sub
''' <summary>
''' 对每个节点执行指定操作
''' </summary>
''' <param name="action">对指定的对象执行操作的方法</param>
''' <param name="includeSubNodes">是否包括子节点</param>
Public Sub ForEach(ByVal action As Action(Of Node(Of T)), ByVal includeSubNodes As Boolean)
Node(Of T).ForEach(Me, action, includeSubNodes)
End Sub
Public Shared Sub ForEach(ByVal node As Node(Of T), ByVal action As Action(Of Node(Of T)), ByVal includeSubNodes As Boolean)
For Each n As Node(Of T) In node.gChildren
action.Invoke(n)
If includeSubNodes Then ForEach(n, action, True)
Next
End Sub
''' <summary>
''' 对每个T对象执行指定操作
''' </summary>
''' <param name="action">对指定的对象执行操作的方法</param>
''' <param name="includeSubNodes">是否包括子节点的T对象</param>
Public Sub ForEach(ByVal action As Action(Of T), ByVal includeSubNodes As Boolean)
Node(Of T).ForEach(Me, action, includeSubNodes)
End Sub
Public Shared Sub ForEach(ByVal node As Node(Of T), ByVal action As Action(Of T), ByVal includeSubNodes As Boolean)
For Each n As Node(Of T) In node.gChildren
action.Invoke(n.Item)
If includeSubNodes Then ForEach(n, action, True)
Next
End Sub
Public Function Clone() As Node(Of T)
Return uSystem.uRuntime.uSerialization.SerializeHelper.Clone(Of Node(Of T))(Me)
End Function
End Class
End Namespace
NodeCollection.vb
Namespace LzmTW.uSystem.uCollection
<Serializable()> _
Public Class NodeCollection(Of T)
Inherits System.Collections.ObjectModel.Collection(Of Node(Of T))
Private gOwner As Node(Of T)
Friend Sub New(ByVal node As Node(Of T))
gOwner = node
End Sub
Public Shadows Function Add(ByVal Value As T) As Node(Of T)
Dim mNode As New Node(Of T)(Value)
Add(mNode)
gOwner.gChildren.Add(mNode)
Return mNode
End Function
Private Shadows Sub Add(ByVal item As Node(Of T))
With item
.gParent = gOwner
.gIsRoot = False
End With
MyBase.Add(item)
End Sub
Public Shadows Sub RemoveAt(ByVal index As Integer)
If Not IsValidIndex(index) Then
Throw New Exception("索引无效")
End If
Dim mNode As Node(Of T) = Me.Item(index)
Remove(mNode)
gOwner.gChildren.Remove(mNode)
End Sub
Public Shadows Sub Remove(ByVal index As Integer)
Me.RemoveAt(index)
End Sub
Private Shadows Function Remove(ByVal item As Node(Of T)) As Boolean
Return MyBase.Remove(item)
End Function
Public Shadows Sub Insert(ByVal index As Integer, ByVal Value As T)
If Not IsValidIndex(index) Then
Throw New Exception("索引无效")
End If
Dim mNode As New Node(Of T)(Value)
Insert(index, mNode)
gOwner.gChildren.Insert(index, mNode)
End Sub
Private Shadows Sub Insert(ByVal index As Integer, ByVal item As Node(Of T))
With item
.gParent = gOwner
.gIsRoot = False
End With
MyBase.Insert(index, item)
End Sub
Public Overloads Sub Clear()
MyBase.Clear()
If gOwner.gChildren.Count > 0 Then gOwner.gChildren.Clear()
End Sub
Private Function IsValidIndex(ByVal index As Integer) As Boolean
If index >= 0 Then
Return index < Me.Count
End If
Return False
End Function
End Class
End Namespace
两个辅助的类,专用于序列化、反射取值赋值用的。
SerializeHelper.vb
Namespace LzmTW.uSystem.uRuntime.uSerialization
Public Class SerializeHelper
Private Sub New()
End Sub
<System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Advanced)> _
Public Shared Function ItemToXml(Of T)(ByVal obj As T) As String
Dim mResult As String = ""
Dim mSerializer As New System.Xml.Serialization.XmlSerializer(GetType(T))
Dim mStringWriter As New System.IO.StringWriter
Using mStringWriter
mSerializer.Serialize(mStringWriter, obj)
mResult = mStringWriter.ToString
mStringWriter.Close()
End Using
Return mResult
End Function
<System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Advanced)> _
Public Shared Function XmlToItem(Of T)(ByVal xml As String) As T
Dim mSerializer As New System.Xml.Serialization.XmlSerializer(GetType(T))
Dim mStringReader As New System.IO.StringReader(xml)
Return CType(mSerializer.Deserialize(mStringReader), T)
End Function
<System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Advanced)> _
Public Shared Sub ItemToXmlFile(Of T)(ByVal filename As String, ByVal obj As T)
Dim XmlWriter As New System.IO.StreamWriter(filename, False, System.Text.Encoding.Default)
Using XmlWriter
XmlWriter.Write(ItemToXml(obj))
XmlWriter.Close()
End Using
End Sub
<System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Advanced)> _
Public Shared Function XmlFileToItem(Of T)(ByVal filename As String) As T
Dim XmlReader As New System.IO.StreamReader(filename, System.Text.Encoding.Default)
Dim mObj As T
Using XmlReader
mObj = XmlToItem(Of T)(XmlReader.ReadToEnd)
XmlReader.Close()
End Using
Return mObj
End Function
<System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Advanced)> _
Public Shared Sub ItemToFormatterFile(Of T)(ByVal filename As String, ByVal formatter As System.Runtime.Serialization.IFormatter, ByVal obj As T)
Dim mFileStream As System.IO.Stream = System.IO.File.Open(filename, System.IO.FileMode.Create)
Using mFileStream
formatter.Serialize(mFileStream, obj)
mFileStream.Close()
End Using
End Sub
<System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Advanced)> _
Public Shared Function FormatterFileToItem(Of T)(ByVal FileName As String, ByVal formatter As System.Runtime.Serialization.IFormatter) As T
Dim mFileStream As System.IO.Stream = System.IO.File.Open(FileName, System.IO.FileMode.Open)
Dim mObj As T
Using mFileStream
mObj = CType(formatter.Deserialize(mFileStream), T)
mFileStream.Close()
End Using
Return mObj
End Function
Public Shared Function Clone(Of T)(ByVal obj As T) As T
Dim tmpT As T
Dim mFormatter As New System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
Dim mMemoryStream As New System.IO.MemoryStream
Using mMemoryStream
mFormatter.Serialize(mMemoryStream, obj)
mMemoryStream.Position = 0
tmpT = CType(mFormatter.Deserialize(mMemoryStream), T)
mMemoryStream.Close()
End Using
Return tmpT
End Function
Public Shared Sub Save(Of T)(ByVal filename As String, ByVal formattype As FormatType, ByVal obj As T)
Select Case formattype
Case formattype.Binary
ItemToFormatterFile(filename, New System.Runtime.Serialization.Formatters.Binary.BinaryFormatter, obj)
Case formattype.Soap
ItemToFormatterFile(filename, New System.Runtime.Serialization.Formatters.Soap.SoapFormatter, obj)
Case formattype.Xml
ItemToXmlFile(filename, obj)
End Select
End Sub
Public Shared Function Load(Of T)(ByVal filename As String, ByVal formattype As FormatType) As T
Select Case formattype
Case formattype.Binary
Return FormatterFileToItem(Of T)(filename, New System.Runtime.Serialization.Formatters.Binary.BinaryFormatter)
Case formattype.Soap
Return FormatterFileToItem(Of T)(filename, New System.Runtime.Serialization.Formatters.Soap.SoapFormatter)
Case formattype.Xml
Return XmlFileToItem(Of T)(filename)
End Select
Return Nothing
End Function
End Class
Public Enum FormatType
Xml
Binary
Soap
End Enum
End Namespace
ReflectionCommonFunction.vb
Namespace LzmTW.uSystem.uReflection
Public Class CommonFunction
Private Sub New()
End Sub
Public Shared Function TypeHasFields(ByVal t As Type) As Boolean
Return t.GetFields.Length > 0
End Function
Public Shared Function CreateTableFromType(ByVal t As Type) As DataTable
Dim tmpTable As New DataTable
If TypeHasFields(t) Then
For Each f As Reflection.FieldInfo In t.GetFields
tmpTable.Columns.Add(f.Name, f.FieldType)
Next
Else
For Each p As Reflection.PropertyInfo In t.GetProperties
If p.CanRead Then tmpTable.Columns.Add(p.Name, p.PropertyType)
Next
End If
Return tmpTable
End Function
Public Shared Function ItemToDataRow(Of T)(ByVal item As T, ByVal table As DataTable) As DataRow
Dim tmpRow As DataRow = table.NewRow
Dim mName As String
Dim mType As Type = GetType(T)
For Each c As DataColumn In table.Columns
mName = c.ColumnName
If TypeHasFields(mType) Then
tmpRow(mName) = mType.GetField(mName).GetValue(item)
Else
tmpRow(mName) = mType.GetProperty(mName).GetValue(item, Nothing)
End If
Next
Return tmpRow
End Function
Public Shared Sub ItemAppendToTable(Of T)(ByVal item As T, ByVal table As DataTable)
table.Rows.Add(ItemToDataRow(Of T)(item, table))
End Sub
Public Shared Sub ItemAppendToTable(Of T)(ByVal items() As T, ByVal table As DataTable)
For Each item As T In items
ItemAppendToTable(Of T)(item, table)
Next
End Sub
Public Shared Function ItemsToTable(Of T)(ByVal items() As T) As DataTable
Dim mTable As DataTable = CreateTableFromType(GetType(T))
If items Is Nothing Then Return mTable
ItemAppendToTable(Of T)(items, mTable)
Return mTable
End Function
End Class
End Namespace
现在可以测试一下。
测试代码:
Public Class Form1
Private gNode As New LzmTW.uSystem.uCollection.Node(Of item)(New item("Root"))
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
gNode.Nodes.add(New item("First"))
gNode.Nodes.Add(New item("Second")).Nodes.Add(New item("Four")).Nodes.Add(New item("Five")).Nodes.Add(New item("Seven"))
gNode.Nodes.Add(New item("Third"))
gNode.Nodes.Insert(1, New item("Six"))
End Sub
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Me.TreeView1.Nodes.Add(gNode.ConvertToTreeNode(True, "Name", True))
Me.DataGridView1.DataSource = gNode.ConvertToDataTable(True, True)
End Sub
End Class
<Serializable()> _
Public Class item
Private gName As String
Private gDeclare As String
Public Property Name() As String
Get
Return gName
End Get
Set(ByVal value As String)
gName = value
End Set
End Property
Public Property [Declare]() As String
Get
Return gDeclare
End Get
Set(ByVal value As String)
gDeclare = value
End Set
End Property
Sub New()
End Sub
Sub New(ByVal name As String)
gName = name
gDeclare = name
End Sub
Sub New(ByVal name As String, ByVal [declare] As String)
gName = name
gDeclare = [declare]
End Sub
End Class
效果: