绑定treeview控件数据应该用什么属性_chart控件绑定数据源

绑定treeview控件数据应该用什么属性_chart控件绑定数据源扩展TreeView控件Windows窗体控件开发示例DuncanMackenzieMicrosoftDeveloperNetwork2002年5月摘要:讲述了如何向TreeView控件添加数据绑定功能,它是一系列MicrosoftWindows控件开发示例之一。您可以将本文与相关的概述文章结合起来阅读。您可以从MSDNCodeCenter

扩展 TreeView 控件


Windows 窗体控件开发示例

Duncan Mackenzie
Microsoft Developer Network
2002 年 5 月

 

摘要:讲述了如何向 TreeView 控件添加数据绑定功能,它是一系列 Microsoft Windows 控件开发示例之一。您可以将本文与相关的概述文章结合起来阅读。

您可以从 MSDN Code Center 下载 WinFormControls.exe(英文)源代码。(请注意,在示例文件中,程序员的注释使用的是英文,本文中将其译为中文是为了便于读者理解。)

本文是介绍如何在 Microsoft® .NET 中开发控件的系列文章中的第四篇(共五篇):

  • Developing Custom Windows Controls Using Visual Basic .NET (overview)(英文)
  • Adding Regular Expression Validation(英文)
  • Combining Multiple Controls into One(英文)
  • 扩展 TreeView 控件
  • Drawing Your Own Controls Using GDI+(英文)

目录

  • 简介
  • 设计数据绑定树视图
  • 实现数据绑定
  • 使用 CurrencyManager 对象
  • 将 DataSource 转变为树
  • 示例应用程序
  • 小结

简介

在可能的情况下,您应该先使用些现成的控件;因为提供的 Microsoft® Windows® 窗体控件中包含大量编码和测试成果,如果您要放弃它们从头开始,无疑是一种巨大的浪费。基于此,在本例中,我将继承一个现有 Windows 窗体控件 TreeView ,然后对其进行自定义。在下载该 TreeView 控件的代码时,您还会得到附加的控件开发示例,以及一个演示如何与其他数据绑定控件一起使用该增强 TreeView 的示例应用程序。

设计数据绑定树视图

对于 Windows 开发人员来说,向 TreeView 控件添加数据绑定是经常会遇到的问题,但由于 TreeView 和其他控件(如 ListBoxDataGrid)存在一个主要差别(即 TreeView 显示分层数据),因而基本控件目前还不支持此功能(也就是说,我们还必须使用它)。给定一个数据表,您就会很清楚如何在 ListBoxDataGrid 中显示该信息,但利用 TreeView 的分层特点来显示同样的数据就不那么简单明了。就个人而言,我在使用 TreeView 显示数据时曾应用过许多不同的方法,但有一种方法最常用:按某些字段将表中的数据分组,如图 1 所示。

绑定treeview控件数据应该用什么属性_chart控件绑定数据源

图 1:在 TreeView 中显示数据

在本例中,我将创建一个 TreeView 控件,在该控件中可传递一个平面数据集(如图 2 所示),并可轻松地生成图 1 所示的结果。

绑定treeview控件数据应该用什么属性_chart控件绑定数据源

图 2:平面结果集,包含创建图 1 所示的树所需的所有信息

在开始编码之前,我为新控件想出了一个可以处理该特定数据集的设计,并希望它能够适用于许多其他类似的情形。添加一个足可以使用大多数平面数据创建分层结构的组集合,在该集合中为每一级分层均指定一个分组字段、显示字段和值字段(任一或所有字段均应相同)。为了将图 2 所示的数据转变成图 1 所示的 TreeView,我的新控件要求您定义两个分组级别 Publisher 和 Title,并将 pub_id 定义为 Publisher 组的分组字段,将 title_id 定义为 Title 组的分组字段。除分组字段以外,还需要为每个组指定显示和值字段,以确定在相应组节点上显示的文本以及用来唯一标识特定组的值。当遇到此类数据时,请使用 pub_name/pub_idtitle/title_id 作为这两个组的显示/值字段。作者信息将变成树的叶节点(分组分层结构末端的节点),您还需要为这些节点指定 ID (au_id) 和显示 (au_lname) 字段。

构建自定义控件时,在开始编码之前确定程序员对该控件的使用方法将有助于提高控件的使用效率。这种情况下,我希望程序员(在给定了前面所示的数据和所需结果的情况下)能够使用如下几行代码完成分组:

        With DbTreeControl
            .ValueMember = "au_id"
            .DisplayMember = "au_lname"

            .DataSource = myDataTable.DefaultView
            .AddGroup("Publisher", "pub_id", "pub_name", "pub_id")
            .AddGroup("Title", "title_id", "title", "title_id")
        End With

注意:这并不是我最终编写的代码行,但两者相差不多。在开发控件的过程中,我意识到需要将与
TreeView 关联的
ImageList 中的图像索引与每个分组级别相关联,因此必须向
AddGroup 方法中额外添加一个参数。

为了真正构建该树,我将浏览数据并查找字段(指定为每个分组的分组值)的更改,同时在必要时创建新分组节点,并针对每个数据项创建一个叶节点。由于存在分组节点,因此总节点数将大于数据源中的项目数,但基础数据中的每个项有且仅有一个叶节点。

绑定treeview控件数据应该用什么属性_chart控件绑定数据源

图 3:分组节点与叶节点

叶节点和分组节点之间的区别(如图 3 所示)对本文的余下部分具有重要意义。我决定将这两类节点区别对待,为每一类节点分别创建自定义节点,并根据所选的节点类型引发不同的事件。

实现数据绑定

为该控件编写代码的第一步是创建项目和相应的起始类。在本例中,我首先创建一个新 Windows 控件库,然后删除默认的 UserControl 类,并用一个从 TreeView 控件继承的新类来代替它:

Public Class dbTreeControl
    Inherits System.Windows.Forms.TreeView

从这时起,我将设计一个可以放入到窗体中的控件,并使其具有常规的 TreeView 的外观和功能。下一步是开始添加旨在处理在 TreeView 中加入的新功能所需的代码,即数据绑定和分组数据。

添加 DataSource 属性

我的新控件的所有功能都很重要,但构建复杂数据绑定控件的两个关键问题是处理 DataSource 属性和从数据源的每个对象中检索单个项目。

创建属性例程

首先,任何用于实现复杂数据绑定的控件都需要实现一个 DataSource 属性例程,并保持适当的成员变量:

Private m_DataSource As Object

<Category("Data")> _
Public Property DataSource() As Object
    Get
        Return m_DataSource
    End Get
    Set(ByVal Value As Object)
        If Value Is Nothing Then
            cm = Nothing
            GroupingChanged()
        Else
            If Not (TypeOf Value Is IList Or _
                      TypeOf Value Is IListSource) Then
                ' 不是针对该用途的有效数据源
                Throw New System.Exception("无效 DataSource")
            Else
                If TypeOf Value Is IListSource Then
                    Dim myListSource As IListSource
                    myListSource = CType(Value, IListSource)
                    If myListSource.ContainsListCollection = True Then
                        Throw New System.Exception("无效 DataSource")
                    Else
                        ' 对,对。它是有效的数据源
                        m_DataSource = Value
                        cm = CType(Me.BindingContext(Value), _
                            CurrencyManager)
                        GroupingChanged()
                    End If
                Else
                    m_DataSource = Value
                    cm = CType(Me.BindingContext(Value), _
                        CurrencyManager)
                    GroupingChanged()
                End If
            End If
        End If
    End Set
End Property

IList 接口

可用作复杂数据绑定数据源的对象通常都支持 IList Interface(英文),该接口将数据公开为对象集合,并提供若干有用属性,如 Count。我的新 TreeView 控件要求在其绑定中使用一个支持 IList 的对象,但使用另一个接口 IListSource Interface(英文)也可以,因为它提供了一个获取 IList 对象的简便方法 (GetList)。当设置 DataSource 属性后,我首先确定是否提供了有效的对象,即一个支持 IListIListSource 的对象。我真正想要的是 IList,因此如果对象仅支持 IListSource(例如 DataTable),那么我将使用该接口的 GetList() 方法获得正确的对象。

某些实现 IListSource 的对象(如 DataSet)实际上包含多个由 ContainsListCollection 属性表示的列表。如果该属性为 True,则 GetList 将返回一个表示列表(包含多个列表)的 IList 对象。在我的示例中,我决定支持直接连接到 IList 对象或仅包含一个 IList 对象的 IListSource 对象,并忽略需要附加工作来指定数据源的对象,如 DataSet

注意:如果要支持此类对象(
DataSet 或与之类似的对象),您可以再添加一个属性(如
DataMember)来指定用于绑定的特定子列表。

如果提供的数据源有效,则最终结果是创建 CurrencyManager Class(英文)的实例 (cm = Me.BindingContext(Value))。由于该实例将用于访问基础数据源、对象属性和位置信息,因此被存储在局部变量中。

添加显示和值成员属性

拥有 DataSource 是实现复杂数据绑定的第一步,但该控件需要了解数据的哪些特定字段或属性将用作显示和值成员。Display 成员将用作树节点的标题,而 Value 成员可通过节点的 Value 属性进行访问。这些属性都是字符串,表示字段或属性名,可以方便地添加到控件中:

    Private m_ValueMember As String
    Private m_DisplayMember As String

    <Category("Data")> _
    Public Property ValueMember() As String
        Get
            Return m_ValueMember
        End Get
        Set(ByVal Value As String)
            m_ValueMember = Value
        End Set
    End Property

    <Category("Data")> _
    Public Property DisplayMember() As String
        Get
            Return m_DisplayMember
        End Get
        Set(ByVal Value As String)
            m_DisplayMember = Value
        End Set
    End Property

在此 TreeView 中,这些属性将仅表示叶节点的 DisplayValue 成员,每个分组级别的相应信息将在 AddGroup 方法中指定。

使用 CurrencyManager 对象

在前面探讨的 DataSource 属性中,创建了一个 CurrencyManager 类的实例,并存储在类级别变量中。通过该对象访问的 CurrencyManager 类是实现数据绑定的关键部分,因为它具有的属性、方法和事件可实现以下功能:

  • 访问数据源的基础 IList 对象
  • 在数据源中检索和设置对象字段或属性,以及
  • 使您的控件与同一窗体中的其他数据绑定控件同步。

检索属性/字段值

CurrencyManager 对象允许您通过它的 GetItemProperties 方法从数据源的单个项中检索属性或字段值,如 DisplayMemberValueMember 字段的值。然后使用 PropertyDescriptor 对象获取特定列表项上的特定字段或属性的值。下面的代码片断显示了这些 PropertyDescriptor 对象的创建方法以及如何使用 GetValue 函数获取基础数据源中某一项的属性值。请注意 CurrencyManager 对象的 List 属性:通过它可以访问该控件绑定到的 IList 实例:

Dim myNewLeafNode As TreeLeafNode
Dim currObject As Object
currObject = cm.List(currentListIndex)
If Me.DisplayMember <> "" AndAlso Me.ValueMember <> "" Then
    ' 添加叶节点?
    Dim pdValue As System.ComponentModel.PropertyDescriptor
    Dim pdDisplay As System.ComponentModel.PropertyDescriptor
    pdValue = cm.GetItemProperties()(Me.ValueMember)
    pdDisplay = cm.GetItemProperties()(Me.DisplayMember)
    myNewLeafNode = _
    New TreeLeafNode(CStr(pdDisplay.GetValue(currObject)), _
          currObject, _
          pdValue.GetValue(currObject), _
          currentListIndex)

GetValue 在返回对象时忽略属性的基本数据类型,因此在使用返回值前需要对其进行转换。

保持数据绑定控件同步

CurrencyManager 还有一个主要功能:除了可以访问绑定数据源和项属性外,它还允许使用相同的 DataSource 来协调该控件和任何其他控件之间的数据绑定。该支持可用于确保多个同时绑定到同一数据源的控件停留在数据源的同一项。对于我的控件而言,我想确保在树中选择项时,其他所有绑定到同一数据源的控件均指向同一项(同一记录、行、甚至数组,如果您愿意从数据库的角度进行思考)。为此,我覆盖了基本 TreeView 中的 OnAfterSelect 方法。在该方法(在选择树节点后被调用)中,我将 CurrencyManager 对象的 Position 属性设置为当前选定项的索引。与该 TreeView 控件一起提供的示例应用程序阐释了同步控件如何使生成数据绑定用户界面变得更为容易。为了使确定当前选定项的列表位置更为容易,我使用了自定义 TreeNode 类(TreeLeafNodeTreeGroupNode),并将每个节点的列表索引存储到创建的 Position 属性中:

Protected Overrides Sub OnAfterSelect _ 
(ByVal e As System.Windows.Forms.TreeViewEventArgs)
    Dim tln As TreeLeafNode
    If TypeOf e.Node Is TreeGroupNode Then
        tln = FindFirstLeafNode(e.Node)
        Dim groupArgs As New groupTreeViewEventArgs(e)
        RaiseEvent AfterGroupSelect(groupArgs)
    ElseIf TypeOf e.Node Is TreeLeafNode Then
        Dim leafArgs As New leafTreeViewEventArgs(e)
        RaiseEvent AfterLeafSelect(leafArgs)
        tln = CType(e.Node, TreeLeafNode)
    End If

    If Not tln Is Nothing Then
        If cm.Position <> tln.Position Then
            cm.Position = tln.Position
        End If
    End If
    MyBase.OnAfterSelect(e)
End Sub

在前面的代码片段中,您可能注意到了一个称为 FindFirstLeafNode 的函数,在此我想对其加以简要介绍。在我的 TreeView 中,只有叶节点(分层结构中的最终节点)才与 DataSource 中的项相对应,其他所有节点只用于创建分组结构。如果我要创建一个性能优良的数据绑定控件,便始终需要选择一个与 DataSource 相对应的项,因此每当选择组节点时,我就会找到该组下的第一个叶节点,就好象该节点是当前的选定内容。您可以检查该示例的运行情况,但现在您大可放心地使用它。

Private Function FindFirstLeafNode(ByVal currNode As TreeNode) _
        As TreeLeafNode
    If TypeOf currNode Is TreeLeafNode Then
        Return CType(currNode, TreeLeafNode)
    Else
        If currNode.Nodes.Count > 0 Then
            Return FindFirstLeafNode(currNode.Nodes(0))
        Else
            Return Nothing
        End If
    End If
End Function

设置 CurrencyManager 对象的 Position 属性可使其他控件与当前选定项同步,但是当其他控件的位置发生变化时,CurrencyManager 也产生事件,以便相应地更改选定项。要成为一个优秀的数据绑定组件,所选内容应随着数据源位置的更改而移动,修改某一项的数据时,显示应随之更新。CurrencyManager 引发的事件共有三个:CurrentChangedItemChangedPositionChanged。最后一个事件相当简单;CurrencyManager 的用途之一是为数据源维护当前位置指示器,以便多个绑定控件均可以显示同一记录或列表项,只要该位置更改,此事件便会引发。其他两个事件有时会相互重叠,因而区别不太明显。以下分别介绍如何在自定义控件中使用这些事件:PositionChanged 是一个比较简单的事件,此处不再赘述;当您要在复杂数据绑定控件(如 Tree)中调整当前选定项时,请使用该事件。只要修改数据源中的项,ItemChanged 事件就会引发,而 CurrentChanged 只有在当前项被修改时才引发。

在我的 TreeView 中,我发现每当我选择一个新项时,所有三个事件均会引发,因此我决定通过更改当前选定项来处理 PositionChanged 事件,而对另外两项不进行任何处理。.NET Framework 文档(英文)建议将数据源强制转换为 IBindingList(如果数据源支持 IBindingList 的话)并改用 ListChanged 事件,但我未实现此功能。

Private Sub cm_PositionChanged(ByVal sender As Object, _
    ByVal e As System.EventArgs) Handles cm.PositionChanged
    Dim tln As TreeLeafNode
    If TypeOf Me.SelectedNode Is TreeLeafNode Then
        tln = CType(Me.SelectedNode, TreeLeafNode)
    Else
        tln = FindFirstLeafNode(Me.SelectedNode)
    End If

    If tln.Position <> cm.Position Then
        Me.SelectedNode = FindNodeByPosition(cm.Position)
    End If
End Sub

Private Overloads Function FindNodeByPosition(ByVal index As Integer) _
        As TreeNode
    Return FindNodeByPosition(index, Me.Nodes)
End Function

Private Overloads Function FindNodeByPosition(ByVal index As Integer, _
    ByVal NodesToSearch As TreeNodeCollection) As TreeNode
    Dim i As Integer = 0
    Dim currNode As TreeNode
    Dim tln As TreeLeafNode

    Do While i < NodesToSearch.Count
        currNode = NodesToSearch(i)
        i += 1
        If TypeOf currNode Is TreeLeafNode Then
            tln = CType(currNode, TreeLeafNode)
            If tln.Position = index Then
                Return currNode
            End If
        Else
            currNode = FindNodeByPosition(index, currNode.Nodes)
            If Not currNode Is Nothing Then
                Return currNode
            End If
        End If
    Loop
    Return Nothing
End Function

将 DataSource 转变为树

编写完数据绑定代码后,我可以继续添加管理分组级别的代码,相应地生成树,然后添加一些自定义事件、方法和属性。

管理组

程序员要配置组集合,就必须创建 AddGroupRemoveGroupClearGroups 函数。每当修改组集合时,都必须重新绘制树(以反映新配置),因此我创建了一个通用过程 GroupingChanged,当情况发生变化,需要强制重建树时,它可以由控件中的各种代码调用:

Private treeGroups As New ArrayList()

Public Sub RemoveGroup(ByVal group As Group)
    If Not treeGroups.Contains(group) Then
        treeGroups.Remove(group)
        GroupingChanged()
    End If
End Sub

Public Overloads Sub AddGroup(ByVal group As Group)
    Try
        treeGroups.Add(group)
        GroupingChanged()
    Catch
    End Try
End Sub

Public Overloads Sub AddGroup(ByVal name As String, _
        ByVal groupBy As String, _
        ByVal displayMember As String, _
        ByVal valueMember As String, _
        ByVal imageIndex As Integer, _
        ByVal selectedImageIndex As Integer)
    Dim myNewGroup As New Group(name, groupBy, _
        displayMember, valueMember, _
        imageIndex, selectedImageIndex)
    Me.AddGroup(myNewGroup)
End Sub


Public Function GetGroups() As Group()
    Return CType(treeGroups.ToArray(GetType(Group)), Group())
End Function

生成树

树的实际重建由一对过程来完成:BuildTreeAddNodes。由于这两个过程的代码太长,本文并未全部列出,而是尽量概括它们的行为(当然,如果愿意您可以下载完整的代码)。如前所述,程序员可以通过设置一系列组与该控件进行交互,然后在 BuildTree 中使用这些组来确定如何设置树节点。BuildTree 清除当前节点集合,然后遍历整个数据源来处理第一级分组(本文前面的示例和图解中提到的 Publisher),为每个不同的分组值添加一个节点(使用示例中的数据,为每个 pub_id 值添加一个节点),然后调用 AddNodes 来填充第一级分组下的所有节点。AddNodes 递归调用自身以处理任意多的级数,必要时可添加组节点和叶节点。使用两个基于 TreeNode 的自定义类以区别组节点和叶节点,并为两类节点提供各自相应的属性。

自定义 TreeView 事件

每当选择一个节点时,TreeView 都会引发两个事件:BeforeSelectAfterSelect。但在我的控件中,我想使组节点和叶节点的事件不同,于是便添加了自己的事件 BeforeGroupSelect/AfterGroupSelectBeforeLeafSelect/AfterLeafSelect,除基本事件外,还引发了自定义事件参数类:

Public Event BeforeGroupSelect _
    (ByVal sender As Object, ByVal e As groupTreeViewCancelEventArgs)
Public Event AfterGroupSelect _
    (ByVal sender As Object, ByVal e As groupTreeViewEventArgs)
Public Event BeforeLeafSelect _
    (ByVal sender As Object, ByVal e As leafTreeViewCancelEventArgs)
Public Event AfterLeafSelect _
    (ByVal sender As Object, ByVal e As leafTreeViewEventArgs)

Protected Overrides Sub OnBeforeSelect _
    (ByVal e As System.Windows.Forms.TreeViewCancelEventArgs)
    If TypeOf e.Node Is TreeGroupNode Then
        Dim groupArgs As New groupTreeViewCancelEventArgs(e)
        RaiseEvent BeforeGroupSelect(CObj(Me), groupArgs)
    ElseIf TypeOf e.Node Is TreeLeafNode Then
        Dim leafArgs As New leafTreeViewCancelEventArgs(e)
        RaiseEvent BeforeLeafSelect(CObj(Me), leafArgs)
    End If
    MyBase.OnBeforeSelect(e)
End Sub

Protected Overrides Sub OnAfterSelect _
    (ByVal e As System.Windows.Forms.TreeViewEventArgs)
    Dim tln As TreeLeafNode
    If TypeOf e.Node Is TreeGroupNode Then
        tln = FindFirstLeafNode(e.Node)
        Dim groupArgs As New groupTreeViewEventArgs(e)
        RaiseEvent AfterGroupSelect(CObj(Me), groupArgs)
    ElseIf TypeOf e.Node Is TreeLeafNode Then
        Dim leafArgs As New leafTreeViewEventArgs(e)
        RaiseEvent AfterLeafSelect(CObj(Me), leafArgs)
        tln = CType(e.Node, TreeLeafNode)
    End If

    If Not tln Is Nothing Then
        If cm.Position <> tln.Position Then
            cm.Position = tln.Position
        End If
    End If
    MyBase.OnAfterSelect(e)
End Sub

自定义节点类(TreeLeafNodeTreeGroupNode)和自定义事件参数类均包括在可下载代码中。

示例应用程序

要全面理解本示例控件中的所有代码,您应该了解它在应用程序中的运行情况。包含的示例应用程序使用 pubs.mdb Access 数据库,并说明 Tree 控件如何与其他数据绑定控件一起创建 Windows 应用程序。本例中,尤其值得注意的主要功能包括树与其他绑定控件的同步以及对数据源执行搜索时树节点的自动选择。

注意:本示例应用程序(名为“TheSample”)包含在本文的 下载中。

绑定treeview控件数据应该用什么属性_chart控件绑定数据源

图 4:数据绑定 TreeView 的演示应用程序

小结

本文介绍的数据绑定 Tree 控件并非适用于所有需要 Tree 控件来显示数据库信息的项目,但它确实介绍了一种可针对个人目的自定义该控件的方法。请记住,您要生成的任何复杂数据绑定控件与 Tree 控件的大部分代码基本相同,您可以通过修改现有代码来简化以后的控件开发过程。

在下一个示例 Drawing Your Own Controls Using GDI+(英文)中,您将看到在不需要使用特定基类(就象我在该控件中继承了 TreeView 控件一样)的情况下,实现数据绑定有一个更容易的方法。

 今天的文章绑定treeview控件数据应该用什么属性_chart控件绑定数据源分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/58689.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注