Click here to Skip to main content
15,920,111 members
Articles / Desktop Programming / Windows Forms

Hierarchical TreeView Control with Data Binding Enabled

Rate me:
Please Sign up or sign in to vote.
4.46/5 (17 votes)
27 Mar 2005CPOL3 min read 279.8K   6.9K   82   32
The control extends standard TreeView control to make it fully data bind.

Sample Image - bindablehierarchicaltree.gif

Introduction

Some time ago, my task was to write something like a virtual file system. Of course, I decided to use typed DataSets because I had already written a framework to work and update them easily. With this technology, it is very easy to display the content of a folder. Relational DataTables are very great tools for this. That’s all right, but when I saw the result - I died! That was not the look and feel that I had mentioned to my client! So I opened Google and started searching for TreeView with data binding enabled. Of course, I found something: this (Data Binding TreeView in C#) was pretty but that was not the hierarchy in my mind; this (How to fill hierarchical data into a TreeView using base classes and data providers) was pretty too but I didn't understand why the author doesn't like standard binding. Both were not for me! And as a real Ukrainian man, I decided to write my own!

Binding Implementation

First of all, we need a method to fill all the data in the tree view. For this, I store references of the data items in the ArrayList. This gives me an indicator of the items that are not in the tree. I will iterate through the items until the length of this array becomes 0. This realization will throw an exception if some of the items cannot find their places in the try. If you need another realization of this (for example, do nothing or store these items on the bottom of the root node), please let me know. I will try to update this article.

C#
ArrayList unsortedNodes = new ArrayList(); 
//This is list of items that still have no place in tree

for (int i = 0; i < this.listManager.Count; i++)
{
    //Fill this list with all items.
    unsortedNodes.Add(this.CreateNode(this.listManager, i));
}

int startCount;

//Iterate until list will not empty.
while (unsortedNodes.Count > 0)
{    
    startCount = unsortedNodes.Count;

    for (int i = unsortedNodes.Count-1; i >= 0 ; i--)
    {                    
        if (this.TryAddNode((DataTreeViewNode)unsortedNodes[i]))
        {
            //Item found its place.
            unsortedNodes.RemoveAt(i);
        }
    }

    if (startCount == unsortedNodes.Count)
    {
        //Throw if nothing was done, in another way this 
        //will continuous loop.
        throw new ApplicationException("Tree view confused 
                      when try to make your data hierarchical.");
    }
}

private bool TryAddNode(DataTreeViewNode node)
{
    if (this.IsIDNull(node.ParentID))
    {
        //If parent is null this mean that this is root node.
        this.AddNode(this.Nodes, node);                
        return true;
    }
    else
    {
        if (this.items_Identifiers.ContainsKey(node.ParentID))
        {
            //Parent already exists in tree so we can add item to it.
            TreeNode parentNode = 
                    this.items_Identifiers[node.ParentID] as TreeNode;
            if (parentNode != null)
            {
                this.AddNode(parentNode.Nodes, node);                
                return true;
            }
        }
    }
    //Parent was not found at this point.
    return false;
}

Respond to External Data Changes

Okay… now we have our tree view filled with all items. Second one that we need is to respond to external data changes. For this, we need to handle the ListChanged event of the current context.

C#
((IBindingList)this.listManager.List).ListChanged += 
                     new ListChangedEventHandler(DataTreeView_ListChanged);

Realization of the handle is very simple.

C#
private void DataTreeView_ListChanged(object sender, ListChangedEventArgs e)
{            
    switch(e.ListChangedType)
    {
        case ListChangedType.ItemAdded:
            //Add item here.
            break;

        case ListChangedType.ItemChanged:
            //Change node associated with this item
            break;

        case ListChangedType.ItemMoved:
            //Parent changed.
            break;

        case ListChangedType.ItemDeleted:
            //Item removed
            break;                    

        case ListChangedType.Reset:
            //This reset all data and control need to refill all data.
            break;
        
    }
}

Now our control particularly supports data binding. You are able to see data, it will change synchronously with external data.

Selected Node

You can ask what additional functionality is required? - Oh! this is only the start.

So the next point: If you change the data source position, our control will not change it. Currency manager has a PositionChanged event. We will use it.

C#
this.listManager.PositionChanged += 
                  new EventHandler(listManager_PositionChanged);

At the start point, we added index for positions and nodes according to them. This gives us an easy way to find a node by its position. So this short code will give us the ability to find the selected node by its position.

C#
DataTreeViewNode node = 
          this.items_Positions[this.listManager.Position] as DataTreeViewNode;
if (node != null)
{
    this.SelectedNode = node;
}

Current Context Position

Now you are not able to use this control as parent to your table. Basically, all that we need is according to the selection of the node, change position of the context. This is not a problem as we store position of the item in each node. Make the AfterSelect event:

C#
private void DataTreeView_AfterSelect(object sender,
                              System.Windows.Forms.TreeViewEventArgs e)
{    
    DataTreeViewNode node = e.Node as DataTreeViewNode;
    if (node != null)
    {
        //Set position.
        this.listManager.Position = node.Position;
    }
}

Label Editing

Not a problem! Just use AfterLabelEdit.

C#
private void DataTreeView_AfterLabelEdit(object sender, 
                           System.Windows.Forms.NodeLabelEditEventArgs e)
{
    DataTreeViewNode node = e.Node as DataTreeViewNode;
    if (node != null)
    {
        //This will found appropriate converter for 
        //type and see if it can convert from string. 
        if (this.PrepareValueConvertor()
          && this.valueConverter.IsValid(e.Label)//Lets converter to check value.
           )
        {
            //Set property.
            this.nameProperty.SetValue(
                this.listManager.List[node.Position],
                this.valueConverter.ConvertFromString(e.Label)
                );
            this.listManager.EndCurrentEdit();
            return;
        }
    }
    //Node text are not editable.
    e.CancelEdit = true;
}

Using the Code

As DataSource you can use any data that, for example, DataGrid can use. You can use any type of columns to bind. Basically, only the name column is limited to types that can be converted from string. This applies only when EditLabel is true.

In most cases, data must have three columns: Identifier, Name and Identifier of parent row. If you need something like 'FirstName + " " + LastName' as Name field - you can make autocomputed columns in DataSet.

I am not including image index in binding because I didn't need it. Let me know if you need this functionality. I will update this article.

Bonuses

First bonus is full design time support. Unlike all other data bound trees on “Code Project”, this tree view has all standard designers that, for example, DataGrid has (of course, with some changes, see Design namespace). Second one is roundup framework bug with bottom scrollbar in TreeView (scroll bar is visible all the time, even if it is not needed at all).

That’s all! Enjoy. Visit my blog for the latest news!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
Ukraine Ukraine

Comments and Discussions

 
GeneralBug after Reset List -> Performance decrease Pin
rodionmarx15-Feb-11 23:53
rodionmarx15-Feb-11 23:53 
Questionproblem when change datasource Pin
msanaei16-Aug-10 21:20
msanaei16-Aug-10 21:20 
GeneralControl Not working for me Pin
Noman Siddiqui12-Apr-10 9:01
Noman Siddiqui12-Apr-10 9:01 
GeneralVB.NET Code Pin
Muhammad Waseem Tahir31-May-08 4:58
professionalMuhammad Waseem Tahir31-May-08 4:58 
GeneralRe: VB.NET Code Pin
gutsfun7-Oct-10 4:30
gutsfun7-Oct-10 4:30 
Here is the code in VB.NET. This is the Jeremy Thomas upgrade version of this control : http://rapiddevbookcode.codeplex.com/SourceControl/changeset/view/63168#945426[^]

Imports System.Collections
Imports System.ComponentModel
Imports System.Diagnostics
Imports System.Drawing
Imports System.Drawing.Design
Imports System.Runtime.InteropServices

''' <summary>
''' Control Treeview permettant un DataBinding hiérachique.
''' </summary>
Public Class DataTreeView
    Inherits TreeView

    Private Const SbHorz As Integer = 0

#Region "Fields"
    Private ReadOnly components As Container
    Private ReadOnly mBindingSource As BindingSource
    Private listManager As CurrencyManager

    Private idPropertyName As String
    Private namePropertyName As String
    Private parentIdPropertyName As String
    Private valuePropertyName As String
    Private ReadOnly DataMemberFieldSeperator() As Char = New Char() {"."c}

    Private idProperty As PropertyDescriptor
    Private nameProperty As PropertyDescriptor
    Private parentIdProperty As PropertyDescriptor
    Private valueProperty As PropertyDescriptor

    Private valueConverter As TypeConverter

    Private selectionChanging As Boolean
#End Region

#Region "Constructors"

    ''' <summary>
    ''' Initialise une nouvelle instance de la classe <see cref="DataTreeView"></see>.
    ''' </summary>
    Public Sub New()
        idPropertyName = String.Empty
        namePropertyName = String.Empty
        parentIdPropertyName = String.Empty
        selectionChanging = False
        mBindingSource = New BindingSource()
        FullRowSelect = True
        HideSelection = False
        HotTracking = True
        AddHandler AfterSelect, AddressOf DataTreeView_AfterSelect
        AddHandler BindingContextChanged, AddressOf DataTreeView_BindingContextChanged
        AddHandler AfterLabelEdit, AddressOf DataTreeView_AfterLabelEdit
        AddHandler DragDrop, AddressOf DataTreeView_DragDrop
        AddHandler DragOver, AddressOf DataTreeView_DragOver
        AddHandler ItemDrag, AddressOf DataTreeView_ItemDrag
    End Sub

    ''' <summary>
    ''' Nettoie toues les ressources en cours d'utilisation.
    ''' </summary>
    Protected Overrides Sub Dispose(ByVal disposing As Boolean)
        If disposing Then
            If components IsNot Nothing Then
                components.Dispose()
            End If
        End If
        MyBase.Dispose(disposing)
    End Sub
#End Region

#Region "Win32"
    ''' <summary>
    ''' Permet la gestion du scrollbar sur le contrôle Treeview via l'API windows.
    ''' </summary>
    ''' <param name="hWnd"></param>
    ''' <param name="wBar"></param>
    ''' <param name="bShow"></param>
    <DllImport("User32.dll")> _
    Private Shared Function ShowScrollBar(ByVal hWnd As IntPtr, ByVal wBar As Integer, ByVal bShow As Boolean) As Boolean
    End Function
#End Region

#Region "Properties"

    ''' <summary>
    ''' Obtient ou définit la DataSource liée au Treeview.
    ''' </summary>
    <AttributeProvider(GetType(IListSource)), Category("Data"), Description("Data source of the tree.")> _
    Public Property DataSource() As Object
        Get
            Return mBindingSource.DataSource
        End Get
        Set(ByVal value As Object)
            If mBindingSource.DataSource IsNot value Then
                mBindingSource.DataSource = value
                ResetData()
            End If
        End Set
    End Property

    ''' <summary>
    ''' Obtient ou définit le membre du DataSource servant de référence de donnée.
    ''' </summary>
    <Editor("System.Windows.Forms.Design.DataMemberListEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", GetType(UITypeEditor)), RefreshProperties(RefreshProperties.Repaint), Category("Data"), Description("Data member of the tree.")> _
    Public Property DataMember() As String
        Get
            Return mBindingSource.DataMember
        End Get
        Set(ByVal value As String)
            If mBindingSource.DataMember <> value Then
                mBindingSource.DataMember = value
                'this.ResetData();
            End If
        End Set
    End Property


    ''' <summary>
    ''' Obtient ou définit l'identifiant du noeud enfant dans le DataSource.
    ''' </summary>
    <DefaultValue(""), Editor("System.Windows.Forms.Design.DataMemberFieldEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", GetType(UITypeEditor)), Category("Data"), Description("Identifier member, in most cases this is primary column of the table.")> _
    Public Property IDColumn() As String
        Get
            Return idPropertyName
        End Get
        Set(ByVal value As String)
            If idPropertyName <> value Then
                If value Is Nothing Then
                    DataMember = String.Empty
                    idPropertyName = String.Empty
                Else
                    Dim split = value.Split(DataMemberFieldSeperator)
                    Dim temp As String
                    If split.Length > 1 Then
                        DataMember = split(0)
                        temp = split(1)
                    Else
                        DataMember = String.Empty
                        temp = value
                    End If
                    idProperty = Nothing
                    If DataSource IsNot Nothing AndAlso temp <> String.Empty Then
                        Dim currencyManager = TryCast(New BindingContext().Item(DataSource, DataMember), CurrencyManager)
                        If currencyManager IsNot Nothing Then
                            Dim propertyDescriptorCollection = currencyManager.GetItemProperties()
                            idProperty = propertyDescriptorCollection.Find(temp, True)
                            If propertyDescriptorCollection.Count = 0 Then
                                idPropertyName = value
                            End If
                        End If
                        If idProperty IsNot Nothing Then
                            idPropertyName = value
                        End If
                    Else
                        idPropertyName = value
                    End If
                End If
                '  this.ResetData();
            End If
        End Set
    End Property

    ''' <summary>
    ''' Obtient ou définit le nom de la colonne du DataSource qui sert à alimenter la propriété text des noeuds.
    ''' </summary>
    <DefaultValue(""), Editor("System.Windows.Forms.Design.DataMemberFieldEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", GetType(UITypeEditor)), Category("Data"), Description("Name member. Note: editing of this column available only with types that support converting from string.")> _
    Public Property NameColumn() As String
        Get
            Return namePropertyName
        End Get
        Set(ByVal value As String)
            If namePropertyName <> value Then
                If value Is Nothing Then
                    namePropertyName = String.Empty
                Else
                    Dim split = value.Split(DataMemberFieldSeperator)
                    Dim temp As String
                    Dim tempDataMember As String
                    If split.Length > 1 Then
                        tempDataMember = split(0)
                        temp = split(1)
                    Else
                        tempDataMember = String.Empty
                        temp = value
                    End If
                    nameProperty = Nothing
                    If DataSource IsNot Nothing AndAlso temp <> String.Empty Then
                        If tempDataMember = DataMember OrElse DataMember = String.Empty Then
                            Dim currencyManager = TryCast(New BindingContext().Item(DataSource, DataMember), CurrencyManager)
                            If currencyManager IsNot Nothing Then
                                If DataMember Is Nothing Then
                                    DataMember = tempDataMember
                                End If
                                Dim propertyDescriptorCollection = currencyManager.GetItemProperties()
                                nameProperty = propertyDescriptorCollection.Find(temp, True)
                                If propertyDescriptorCollection.Count = 0 Then
                                    namePropertyName = value
                                End If
                            End If
                            If nameProperty IsNot Nothing Then
                                namePropertyName = value
                            End If
                        End If
                    Else
                        namePropertyName = value
                    End If
                End If
                '  this.ResetData();
            End If
        End Set
    End Property

    ''' <summary>
    ''' Obtient ou définit l'identifiant du noeud parent dans le DataSource.
    ''' </summary>
    <DefaultValue(""), Editor("System.Windows.Forms.Design.DataMemberFieldEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", GetType(UITypeEditor)), Category("Data"), Description("Identifier of the parent. Note: this member must have the same type as identifier column.")> _
    Public Property ParentIDColumn() As String
        Get
            Return parentIdPropertyName
        End Get
        Set(ByVal value As String)
            If namePropertyName <> value Then
                If value Is Nothing Then
                    namePropertyName = String.Empty
                Else
                    Dim split = value.Split(DataMemberFieldSeperator)
                    Dim temp As String
                    Dim tempDataMember As String
                    If split.Length > 1 Then
                        tempDataMember = split(0)
                        temp = split(1)
                    Else
                        tempDataMember = String.Empty
                        temp = value
                    End If
                    parentIdProperty = Nothing
                    If DataSource IsNot Nothing AndAlso temp <> String.Empty Then
                        If tempDataMember = DataMember OrElse DataMember = String.Empty Then
                            Dim currencyManager = TryCast(New BindingContext().Item(DataSource, DataMember), CurrencyManager)
                            If currencyManager IsNot Nothing Then
                                If DataMember Is Nothing Then
                                    DataMember = tempDataMember
                                End If
                                Dim propertyDescriptorCollection = currencyManager.GetItemProperties()
                                parentIdProperty = propertyDescriptorCollection.Find(temp, True)
                                If propertyDescriptorCollection.Count = 0 Then
                                    parentIdPropertyName = value
                                End If
                            End If
                            If parentIdProperty IsNot Nothing Then
                                parentIdPropertyName = value
                            End If
                        End If
                    Else
                        parentIdPropertyName = value
                    End If
                End If
                '  this.ResetData();
            End If
        End Set
    End Property

#End Region

#Region "Events"

    ''' <summary>
    ''' Définit la propriété <c>Position</c> du DataSource sur un item précis.
    ''' </summary>
    ''' <param name="itemToSelect"></param>
    ''' <remarks></remarks>
    Public Sub MoveToItem(ByVal itemToSelect As Object)
        If itemToSelect IsNot Nothing AndAlso listManager IsNot Nothing Then
            listManager.Position = listManager.List.IndexOf(itemToSelect)
        End If
    End Sub

    ''' <summary>
    ''' Se produit après la selection d'un noeud du Treeview.
    ''' </summary>
    ''' <param name="sender">Source de l'événement.</param>
    ''' <param name="e">Objet <see cref="TreeViewEventArgs"></see> contenant les données d'événement.</param>
    ''' <remarks>Permet de positionner le curseur du DataSource sur le nouveau noeud sélectionné.</remarks>
    Private Sub DataTreeView_AfterSelect(ByVal sender As Object, ByVal e As TreeViewEventArgs)
        If Not selectionChanging Then
            BeginSelectionChanging()
            Try
                If e.Node IsNot Nothing Then
                    MoveToItem(e.Node.Tag)
                End If
            Finally
                EndSelectionChanging()
            End Try
        End If
    End Sub

    ''' <summary>
    ''' Se produit après l'édition du noeud courant.
    ''' </summary>
    ''' <param name="sender">Source de l'événement.</param>
    ''' <param name="e">Objet <see cref="NodeLabelEditEventArgs"></see> contenant les données d'événement.</param>
    ''' <remarks>Permet de valider le contenu du noeud.</remarks>
    Private Sub DataTreeView_AfterLabelEdit(ByVal sender As Object, ByVal e As NodeLabelEditEventArgs)
        If e.Node IsNot Nothing AndAlso e.Label IsNot Nothing Then
            If PrepareValueConvertor() AndAlso valueConverter.IsValid(e.Label) Then
                nameProperty.SetValue(e.Node.Tag, valueConverter.ConvertFromString(e.Label))
                listManager.EndCurrentEdit()
                Return
            End If
        End If
        e.CancelEdit = True
    End Sub

    ''' <summary>
    ''' Se produit lors d'une action de Drag'n drop.
    ''' </summary>
    ''' <param name="sender">Source de l'événement.</param>
    ''' <param name="e">Objet <see cref="ItemDragEventArgs"></see> contenant les données d'événement.</param>
    Private Sub DataTreeView_ItemDrag(ByVal sender As Object, ByVal e As ItemDragEventArgs)
        DoDragDrop(e.Item, DragDropEffects.Copy Or DragDropEffects.Move)
        ' Demarre l'opération de drag'n drop.
    End Sub

    ''' <summary>
    ''' Se produit une fois l'item relaché après un Drag'n drop.
    ''' </summary>
    ''' <param name="sender">Source de l'événement.</param>
    ''' <param name="e">Objet <see cref="DragEventArgs"></see> contenant les données d'événement.</param>
    Private Sub DataTreeView_DragOver(ByVal sender As Object, ByVal e As DragEventArgs)
        Dim tv = DirectCast(sender, TreeView)
        Dim position = tv.PointToClient(New Point(e.X, e.Y))
        ' Obtient les coordonnées de la sourie.
        Dim destinationNode = tv.GetNodeAt(position)
        ' Obtient le noeud à cette position.
        tv.SelectedNode = destinationNode
        ' Met en surbrillance le noeud trouvé.
        e.Effect = DragDropEffects.Move
    End Sub

    Private Sub DataTreeView_DragDrop(ByVal sender As Object, ByVal e As DragEventArgs)
        Dim dragNode As TreeNode
        ' Référence au noeud en courant de déplacement.
        Dim dropNode As TreeNode
        ' Référence au nouveau noeud père.
        If e.Data.GetDataPresent(GetType(TreeNode)) Then
            ' Vérifie que l'objet déplacé est bien un noeud.
            Dim tv = DirectCast(sender, TreeView)
            Dim position = tv.PointToClient(New Point(e.X, e.Y))
            ' Obtient les coordonnées de la sourie.
            dropNode = tv.GetNodeAt(position)
            ' Obtient le noeud à cette position.
            dragNode = DirectCast(e.Data.GetData(GetType(TreeNode)), TreeNode)
            ' Référence au noeud déplacé. 
            If (dropNode IsNot Nothing AndAlso dragNode IsNot Nothing) AndAlso (dropNode IsNot dragNode) Then
                ' Vérifie que le noeud déplacé n'est pas remit sur lui même et que chaque référence existe.
                ChangeParent(dragNode, dropNode)
                dropNode.ExpandAll()
            End If
        End If
    End Sub

    ''' <summary>
    ''' Se produit lors d'un changement de données sur le DataSource.
    ''' </summary>
    ''' <param name="sender">Source de l'événement.</param>
    ''' <param name="e">Objet <see cref="EventArgs"></see> contenant les données d'événement.</param>
    ''' <remarks>Réinitialise l'affichage du Treeview.</remarks>
    Private Sub DataTreeView_BindingContextChanged(ByVal sender As Object, ByVal e As EventArgs)
        ResetData()
    End Sub

    ''' <summary>
    ''' Se produit lors d'un changement de position dans le Treeview.
    ''' </summary>
    ''' <param name="sender">Source de l'événement.</param>
    ''' <param name="e">Objet <see cref="EventArgs"></see> contenant les données d'événement.</param>
    ''' <remarks>Actualise l'affichage du Treeview.</remarks>
    Private Sub ListManagerPositionChanged(ByVal sender As Object, ByVal e As EventArgs)
        If PropertyAreSet() Then
            SynchronizeSelection()
        Else
            ResetData()
        End If
    End Sub

    Private handelingItemChanged As Boolean

    ''' <summary>
    ''' Se produit lors d'un changement de structure dans le Treeview.
    ''' </summary>
    ''' <param name="sender">Source de l'événement.</param>
    ''' <param name="e">Objet <see cref="ListChangedEventArgs"></see> contenant les données d'événement.</param>
    ''' <remarks>Actualise l'affichage du Treeview.</remarks>
    Private Sub DataTreeView_ListChanged(ByVal sender As Object, ByVal e As ListChangedEventArgs)
        If PropertyAreSet() Then
            Select Case e.ListChangedType
                Case ListChangedType.ItemAdded
                    Try
                        If Not IsIDNull(GetCurrentID(e.NewIndex)) Then
                            If TryAddNode(CreateNode(e.NewIndex)) Then
                                Trace.Write(e)
                            End If
                        End If
                    Catch ae As ArgumentException
                        Trace.Write(ae)
                    End Try
                    Exit Select

                Case ListChangedType.ItemChanged
                    If Not handelingItemChanged Then
                        Try
                            handelingItemChanged = True
                            Dim changedNode = GetDatasNode(e.NewIndex)
                            If changedNode IsNot Nothing Then
                                RefreshData(changedNode, e.NewIndex)
                            ElseIf IsIDNull(GetCurrentID(e.NewIndex)) Then
                                Throw New ApplicationException("Item not found or wrong type.")
                            ElseIf TryAddNode(CreateNode(e.NewIndex)) Then
                                ResetData()
                            Else
                                Throw New ApplicationException("Item not found or wrong type.")
                            End If
                        Finally
                            handelingItemChanged = False
                        End Try
                    End If
                    Exit Select

                Case ListChangedType.ItemDeleted
                    If SelectedNode IsNot Nothing AndAlso ((listManager.List.IndexOf(SelectedNode.Tag) = -1) OrElse listManager.List.IndexOf(SelectedNode.Tag) = e.NewIndex) Then
                        SelectedNode.Remove()
                        RefreshAllData(e.NewIndex)
                    End If
                    Exit Select

                Case ListChangedType.Reset
                    ResetData()
                    Exit Select

                Case ListChangedType.ItemMoved
                    Exit Select
            End Select
        Else
            ResetData()
        End If
    End Sub

#End Region

#Region "Implementation"

    Private Sub Clear()
        Nodes.Clear()
    End Sub

    Private Function PrepareDataSource() As Boolean
        If BindingContext IsNot Nothing Then
            If mBindingSource.DataSource IsNot Nothing Then
                listManager = TryCast(BindingContext(mBindingSource.DataSource, mBindingSource.DataMember), CurrencyManager)
                Return listManager IsNot Nothing
            Else
                listManager = Nothing
                Clear()
            End If
        End If
        Return False
    End Function

#Region "Descriptors"

    Private Function PropertyAreSet() As Boolean
        Return (idProperty IsNot Nothing AndAlso nameProperty IsNot Nothing AndAlso parentIdProperty IsNot Nothing)
    End Function

    Private Function PrepareDescriptors() As Boolean
        If idPropertyName.Length <> 0 AndAlso namePropertyName.Length <> 0 AndAlso parentIdPropertyName.Length <> 0 Then
            Dim propertyDescriptorCollection As PropertyDescriptorCollection = listManager.GetItemProperties()
            'APropertyDescriptorCollection.Find
            If idProperty Is Nothing Then
                idProperty = propertyDescriptorCollection(idPropertyName)
            End If
            If nameProperty Is Nothing Then
                nameProperty = propertyDescriptorCollection(namePropertyName)
            End If
            If parentIdProperty Is Nothing Then
                parentIdProperty = propertyDescriptorCollection(parentIdPropertyName)
            End If
        End If

        Return PropertyAreSet()
    End Function

    Private Function PrepareValueDescriptor() As Boolean
        If valueProperty Is Nothing Then
            If valuePropertyName = String.Empty Then
                valuePropertyName = idPropertyName
            End If
            valueProperty = listManager.GetItemProperties()(valuePropertyName)
        End If

        Return (valueProperty IsNot Nothing)
    End Function

    Private Function PrepareValueConvertor() As Boolean
        If valueConverter Is Nothing Then
            valueConverter = TypeDescriptor.GetConverter(nameProperty.PropertyType)
        End If

        Return (valueConverter IsNot Nothing AndAlso valueConverter.CanConvertFrom(GetType(String)))
    End Function

#End Region

    Private Sub WireDataSource()
        AddHandler listManager.PositionChanged, AddressOf ListManagerPositionChanged
        AddHandler DirectCast(listManager.List, IBindingList).ListChanged, AddressOf DataTreeView_ListChanged
    End Sub

    ''' <summary>
    ''' Initialise le listing du Treeview.
    ''' </summary>
    Public Sub ResetData()
        BeginUpdate()

        Clear()

        Try
            If PrepareDataSource() Then
                WireDataSource()
                If PrepareDescriptors() Then
                    Dim unsortedNodes = New ArrayList()

                    For i As Integer = 0 To listManager.Count - 1
                        unsortedNodes.Add(CreateNode(i))
                    Next

                    Dim startCount As Integer

                    While unsortedNodes.Count > 0
                        startCount = unsortedNodes.Count

                        For i As Integer = unsortedNodes.Count - 1 To 0 Step -1
                            If TryAddNode(DirectCast(unsortedNodes(i), TreeNode)) Then
                                unsortedNodes.RemoveAt(i)
                            End If
                        Next

                        If startCount = unsortedNodes.Count Then
                            Dim AE As ApplicationException = New ApplicationException("Tree view confused when try to make your data hierarchical.")
                            For Each node As TreeNode In unsortedNodes
                                AE.Data.Add(node.ToString(), node)
                            Next
                            Throw AE
                        End If
                    End While
                End If
            End If
        Finally
            EndUpdate()
        End Try
    End Sub

    Private Function TryAddNode(ByVal node As TreeNode) As Boolean
        If HasParent(node) Then
            Dim parentNode = GetDataParent(node)
            If parentNode IsNot Nothing AndAlso Not parentNode.Nodes.Contains(node) Then
                AddNode(parentNode.Nodes, node)
                Return True
            End If
        Else
            AddNode(Nodes, node)
            Return True
        End If
        Return False
    End Function

    Private Shared Sub AddNode(ByVal nodes As TreeNodeCollection, ByVal node As TreeNode)
        nodes.Add(node)
    End Sub

    Private Sub ChangeParent(ByVal childnode As TreeNode, ByVal parentNode As TreeNode)
        If childnode IsNot Nothing AndAlso parentNode IsNot Nothing Then
            Dim ParentID = idProperty.GetValue(parentNode.Tag)
            If PrepareValueConvertor() AndAlso valueConverter.IsValid(ParentID) Then
                parentIdProperty.SetValue(childnode.Tag, ParentID)
                listManager.EndCurrentEdit()
                Return
            End If
        End If
    End Sub

    Private Function FindFirst(ByVal iD As Object) As TreeNode
        Dim foundNodes() As TreeNode = Nodes.Find(iD.ToString(), True)
        If foundNodes.Length = 0 Then
            Return Nothing
        Else
            Return foundNodes(0)
        End If
    End Function

    Private Function GetCurrentID() As Object
        Return idProperty.GetValue(listManager.Current)
    End Function

    Private Function GetCurrentID(ByVal index As Integer) As Object
        Return idProperty.GetValue(listManager.List(index))
    End Function

    Private Function GetCurrentNodeFromData() As TreeNode
        Return GetDatasNode(listManager.Current)
    End Function

    Private Function GetDatasNode(ByVal dataObject As Object) As TreeNode
        Dim dataID = idProperty.GetValue(dataObject)
        Return FindFirst(dataID)
    End Function

    Private Function GetDatasNode(ByVal position As Integer) As TreeNode
        Return GetDatasNode(listManager.List(position))
    End Function

    Private Function GetDataParent(ByVal node As TreeNode) As TreeNode
        Return FindFirst(parentIdProperty.GetValue(node.Tag))
    End Function

    Private Sub ChangeParent(ByVal node As TreeNode)
        Dim currentParentID As Object = Nothing
        Dim dataParentID = parentIdProperty.GetValue(node.Tag)
        If dataParentID IsNot Nothing Then
            If node.Parent IsNot Nothing Then
                currentParentID = idProperty.GetValue(node.Parent.Tag)
            End If
            If Not dataParentID.Equals(currentParentID) Then
                If HasParent(node, dataParentID) OrElse node.Parent IsNot Nothing Then
                    node.Remove()
                    If node.Nodes.Find(dataParentID.ToString(), True).Length > 0 Then
                        Throw New ApplicationException("A Parent can't be the child of a child!")
                    End If
                    Dim newParentNode = FindFirst(dataParentID)
                    If newParentNode IsNot Nothing Then
                        newParentNode.Nodes.Add(node)
                    End If
                End If
            End If
        End If
    End Sub

    Private Function GetID(ByVal node As TreeNode) As Object
        Return idProperty.GetValue(node.Tag)
    End Function

    Public Sub RefreshData(ByVal node As TreeNode, ByVal Data As Object)
        node.Tag = Data
        node.Name = GetID(node).ToString()
        Try
            node.Text = DirectCast(nameProperty.GetValue(node.Tag), String)
        Catch e As Exception
            node.Text = e.Message
        End Try
        Me.ChangeParent(node)
    End Sub

    Public Sub RefreshData(ByVal node As TreeNode)
        Me.RefreshData(node, listManager.Current)
    End Sub

    Public Sub RefreshData(ByVal node As TreeNode, ByVal position As Integer)
        Me.RefreshData(node, listManager.List(position))
    End Sub

    Public Sub RefreshData(ByVal position As Integer)
        Dim DataObject = listManager.List(position)
        Dim Node = GetDatasNode(DataObject)
        If Node IsNot Nothing Then
            Me.RefreshData(Node, DataObject)
        End If
    End Sub

    Public Sub RefreshAllData(ByVal fromPosition As Integer)
        For i As Integer = fromPosition To listManager.Count - 1
            Me.RefreshData(i)
        Next
    End Sub

    Private Sub SynchronizeSelection()
        If Not selectionChanging Then
            BeginSelectionChanging()
            Try
                SelectedNode = GetCurrentNodeFromData()
            Finally
                EndSelectionChanging()
            End Try
        End If
    End Sub

    ''' <summary>
    ''' Génère un nouvel objet <see cref="TreeNode"></see>.
    ''' </summary>
    ''' <param name="position">Position désiré dans le treeview.</param>
    ''' <returns></returns>
    Private Function CreateNode(ByVal position As Integer) As TreeNode
        Dim node = New TreeNode()
        Me.RefreshData(node, position)
        Return node
    End Function

    Private Function CreateNode() As TreeNode
        Dim node = New TreeNode()
        Me.RefreshData(node)
        Return node
    End Function

    Private Shared Function IsIDNull(ByVal id As Object) As Boolean
        If id Is Nothing OrElse Convert.IsDBNull(id) Then
            Return True
        End If
        If id.[GetType]() Is GetType(String) Then
            Return (DirectCast(id, String).Length = 0)
        End If
        If id.[GetType]() Is GetType(Guid) Then
            Return (DirectCast(id, Guid) = Guid.Empty)
        End If
        If id.[GetType]() Is GetType(Integer) Then
            Return (CInt(id) = 0)
        End If
        Return False
    End Function

    Private Shared Function ObjectValuesEqual(ByVal object1 As Object, ByVal object2 As Object) As Boolean
        Return object1.Equals(object2) OrElse (object1.[GetType]() Is object2.[GetType]() AndAlso object1.GetHashCode() = object2.GetHashCode())
    End Function

    Private Function HasParent(ByVal node As TreeNode, ByVal parentID As Object) As Boolean
        Return Not (IsIDNull(parentID) OrElse ObjectValuesEqual(parentID, GetID(node)))
    End Function

    Private Function HasParent(ByVal node As TreeNode) As Boolean
        Return HasParent(node, parentIdProperty.GetValue(node.Tag))
    End Function

    Protected Overrides Sub InitLayout()
        MyBase.InitLayout()
        ShowScrollBar(Handle, SbHorz, False)
    End Sub

    Private Sub BeginSelectionChanging()
        selectionChanging = True
    End Sub

    Private Sub EndSelectionChanging()
        selectionChanging = False
    End Sub

#End Region

End Class

QuestionAbout Gridview control in asp.net 2.0 + Data binding Pin
Samiullah27-May-08 20:46
Samiullah27-May-08 20:46 
QuestionRecursive Pin
danielab6-Sep-07 7:01
danielab6-Sep-07 7:01 
QuestionTreeView COnfused when using filtered BindingSOurce Pin
corsairX9-Nov-06 22:21
corsairX9-Nov-06 22:21 
QuestionDo we have the right version? Pin
throwit19-Aug-06 19:43
throwit19-Aug-06 19:43 
GeneralAbout hide the HScrollbar in TreeView Pin
bjleo13-Feb-06 23:33
bjleo13-Feb-06 23:33 
GeneralRe: About hide the HScrollbar in TreeView Pin
Mike Chaliy13-Feb-06 23:45
Mike Chaliy13-Feb-06 23:45 
QuestionChanging Parent Bug Pin
Emma M17-Jan-06 23:21
Emma M17-Jan-06 23:21 
QuestionImplementing Drag and Drop Pin
Emma M16-Jan-06 6:13
Emma M16-Jan-06 6:13 
AnswerRe: Implementing Drag and Drop Pin
Mike Chaliy16-Jan-06 10:11
Mike Chaliy16-Jan-06 10:11 
GeneralRe: Implementing Drag and Drop Pin
Emma M16-Jan-06 22:34
Emma M16-Jan-06 22:34 
AnswerRe: Implementing Drag and Drop Pin
Jeremy Thomas13-Feb-06 16:02
Jeremy Thomas13-Feb-06 16:02 
GeneralRe: Implementing Drag and Drop Pin
Emma M13-Feb-06 22:10
Emma M13-Feb-06 22:10 
GeneralRe: Implementing Drag and Drop Pin
SmithSystems24-Feb-06 16:39
SmithSystems24-Feb-06 16:39 
GeneralRe: Implementing Drag and Drop Pin
NickOberoi13-Feb-08 11:08
NickOberoi13-Feb-08 11:08 
GeneralRe: Implementing Drag and Drop Pin
Jeremy Thomas13-Jan-10 0:03
Jeremy Thomas13-Jan-10 0:03 
GeneralRe: Implementing Drag and Drop Pin
MarkJoel6025-Aug-10 4:04
MarkJoel6025-Aug-10 4:04 
Generalbug not only when delete Pin
byx4523-Oct-05 9:10
byx4523-Oct-05 9:10 
GeneralRe: bug not only when delete Pin
Mike Chaliy11-Nov-05 3:08
Mike Chaliy11-Nov-05 3:08 
GeneralProblems using custom collection Pin
hcan21-Jul-05 10:32
hcan21-Jul-05 10:32 
GeneralRe: Problems using custom collection Pin
Mike Chaliy11-Nov-05 3:07
Mike Chaliy11-Nov-05 3:07 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.