none
Sorting Treenodes in treeview RRS feed

  • Question

  • I am trying to figure out how to sort the treenodes in a treeview.  I am allowing the user to add and update the nodes and their text at runtime.  Therefore, I need to implement the Sort procedure in the AfterLabelEdit event.  However, there are a few problems I'm not sure how to address.  Has anyone done this?  I can't seem to find a good answer.

    My treeview is only 1 level deep.  A parent node can only have child nodes and those child nodes cannot have any child nodes.  When I add a child node, it adds it at the bottom initially with some default text 'New item' and goes into Edit mode.  After I am done editing the text, the AfterLabelEdit event is fired and thats where I save the node to the database and where I would ideally like to resort ONLY that parents nodes that I just added.  I don't see a need to resort the whole treeview when I only want to sort the current parent and those nodes.

    Does anyone have any thoughts on how to implement this?  The problem is that the new node Text value is still set to 'New Item' in the AfterLabelEdit event and therefore if I sort it, I will need to use the new value entered, e.Label and not e.Text.

    Thoughts or comments?

    Sunday, June 4, 2006 12:15 AM

All replies

  • Hi,
    the easiest way to sort a treeview is to make your own NodeSorter and set it in the TreeViewNodeSorter property and call Sort() method in the AfterLabelEdit event. Check this thread:
    http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=339694&SiteID=1
    and this link:
    http://msdn2.microsoft.com/en-US/library/system.windows.forms.treeview.treeviewnodesorter.aspx
    They might help.
    Sunday, June 4, 2006 3:00 AM
  • I have actually been trying that and now I remember what my problem was.  (Its been a couple of months since I looked at this.)

    My problem is that the NodeSorter class works fine if I call it from a button click after I have added the new node.  If I try to call it in the AfterLabelEdit event, it sorts the default text of the new node ('New Folder') and makes the last node the text that I entered, overwriting the last node text.

    Is there another event that fires after the AfterLabelEdit event that I could use to trigger the NodeSorter automatically when adding new nodes?

    Thanks,
    Greg

    Sunday, June 4, 2006 3:23 AM
  • Hi there,

    Here's something I knocked together....It's not good or fancy like a NodeSorter but it seems to work OK if you put it in the AfterLabelEdit event.

    Just one thing, it seems to result in the unusual behaviour of retaining the focus/editability on the node whose editing fired the AfterLabelEdit event. I don't think it's a big deal but it could be annoying....Haven't figured out a workaround yet but might update this post when I do.

    Private Sub TreeView1_AfterLabelEdit(ByVal sender As System.Object, ByVal e As System.Windows.Forms.NodeLabelEditEventArgs) Handles TreeView1.AfterLabelEdit
        Dim theParentNode As TreeNode = e.Node.Parent
        Dim indexInCollection As Integer = e.Node.Index
        Dim newPos As Integer = 0
        Dim sortedNodes As List(Of String) = New List(Of String)
        Dim newLabelText As String = e.Label

        'We do not need to fire if nothing has changed (e.Label = e.Node.Text)
        'We do not want infinite firing (IsNothing(e.Label))
        If e.Label = e.Node.Text Or IsNothing(e.Label) Then
            Exit Sub
        End If

        If IsNothing(theParentNode) Then 'Top level node
            For Each node As TreeNode In TreeView1.Nodes
                If node.Index = indexInCollection Then
                    sortedNodes.Add(newLabelText)
                Else
                    sortedNodes.Add(node.Text)
                End If
        Next
        sortedNodes.Sort()

        newPos = sortedNodes.IndexOf(newLabelText)
        TreeView1.Nodes.Remove(e.Node)
        TreeView1.Nodes.Insert(newPos, e.Node)
       Else '1st level node
            For Each node As TreeNode In theParentNode.Nodes
                If node.Index = indexInCollection Then
                    sortedNodes.Add(newLabelText)
                Else
                    sortedNodes.Add(node.Text)
                End If
            Next
            sortedNodes.Sort()

            newPos = sortedNodes.IndexOf(newLabelText)
            theParentNode.Nodes.Remove(e.Node)
            theParentNode.Nodes.Insert(newPos, e.Node)
        End If
    End Sub

    Hope that helps a bit, but sorry if it doesn't
    Sunday, June 4, 2006 5:39 AM
  • Yea, I saw a post very similar to that but didn't like the idea of removing and adding the nodes back but I believe it would work.  I may end up trying it if I can't deal with the resort manual button.

    What I need is another event like LabelEditedComplete or something that fires after the AfterLabelEdit event so that the sort can take place.  Oh well...

    Thanks for the help!

    Greg

    Sunday, June 4, 2006 12:55 PM
  • Hi Greg,

    I think another way was to use a timer.
    We can enable the timer after the added the node. And after you sort the treeview, stop the timer.

    If you still have any concern, please feel free to post here.

    Best regards,
    Peter Huang

    Monday, June 5, 2006 6:07 AM
  • Now that's an interesting approach/hack.  I will give it a try and let you know how it works.

    Thanks,
    Greg

    Wednesday, June 7, 2006 12:19 AM
  • I have this working now where it automatically resorts after adding a node.  Here is how.

    I initially used a System.Timers.Timer object however, I soon found out that if you start the Timer in the AfterLabelEdit event, and then try to do something in the Timer_Tick event, you will get an exception stating that you cannot make cross-thread calls this way.  So...plan B.

    I used the BackgroundWorker component.  This is the preferred way to implement multithreading.  The Worker thread runs your DoWork event and then runs the RunWorkerCompleted event.  So, here is my solution:

    In the AfterLabelEdit event, after I added my node and saved it to the database, I called mybgWorker.RunWorkerAsync() after all my event processing was done.  I then added the following bgWorker events:

    Private Sub bgWorker_RunWorkerCompleted(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs) Handles bgWorker.RunWorkerCompleted
     tvMain.TreeViewNodeSorter = New NodeSorter()
    End Sub

    Private Sub bgWorker_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs) Handles bgWorker.DoWork
     Thread.Sleep(100)
    End Sub

    And thats it.  There is a little flicker when the tree resorts but so far it looks like this will work.

    Thoughts or questions?

    Greg

    Friday, June 9, 2006 1:41 AM
  • Hi Greg

    I also used this approach. I got a few tips for you:

    • In stead of creating a new NodeSorter() you can simply call the Sort() method (assign the NodeSorter after the InitializeComponent() call)
    • Put a Treeview.BeginUpdate() before, and a Treeview.EndUpdate() after calling Treeview.Sort() to prevent flicker and let sorting work faster because the treeview isn't updated when sorting.
    • Is is best to check whether the background worker is busy, before calling RunWorkerAsync(). You can do this by defining a private boolean for a resort after the worker has completed. (A 10th of a second gives this a very small chance, though you never know for sure...)

    Hope this helps.

    Best regards,


    Jeroen Landheer.

    Sunday, June 25, 2006 10:53 PM
  • Thanks, the BeginUpdate and EndUpdate took care of the flicker problem.

    Greg

    Friday, June 30, 2006 2:51 AM
  • Instead of a timer, you could also use BeginInvoke. This would ensure that the AfterLabelEdit event was complete before processing.

     

    I added this to the end of my AfterLabelEdit code:

     

    Me.BeginInvoke(New SortDelegate(AddressOf ProcessTreeViewSort))

     

    And I added this:

    Code Snippet

     

    ''' <summary>

    ''' Delegate needed to process the sorting after a label edit.

    ''' </summary>

    ''' <remarks>

    ''' This is required because the label does not actually change on a node

    ''' until *after* the AfterLabelEdit code is executed. So sorting in

    ''' this event was sorting *before* the label was changed. Using this

    ''' delegate ensures that the AfterLabelEdit event is complete before

    ''' executing the sort.

    ''' </remarks>

    Delegate Sub SortDelegate()

     

    #Region " ProcessTreeViewSort"

    ''' <summary>

    ''' Sorts the nodes of the tree

    ''' </summary>

    ''' <remarks></remarks>

    Public Sub ProcessTreeViewSort()

       ' Resort the entries

       ListTreeView.BeginUpdate()

       ListTreeView.Sort()

       ListTreeView.EndUpdate()

    End Sub

    #End Region

     

     

    Thursday, June 12, 2008 9:02 PM