views:

2927

answers:

1

I have an IEnumerable(of Employee) with a ParentID/ChildID relationship with itself that I can databind to a TreeView and it populates the hierarchy perfectly. However, I want to be able to manually loop through all the records and create all the nodes programmatically so that I can change the attributes for each node based on the data for that given item/none.

Is there a tutorial out there that explains how to do this? I've seen many that use datasets and datatables but none that show how to do it in Linq to SQL (IEnumerable)

UPDATE:

Here's how I used to do it with a DataSet - I just can't seem to find how to do the same with IEnumerable.

Private Sub GenerateTreeView()
        Dim ds As New DataSet()

        Dim tasktree As New Task(_taskID)

        Dim dt As DataTable = tasktree.GetTaskTree()

        ds.Tables.Add(dt)

        ds.Relations.Add("NodeRelation", dt.Columns("TaskID"), dt.Columns("ParentID"))

        Dim dbRow As DataRow
        For Each dbRow In dt.Rows
            If dbRow("TaskID") = _taskID Then
                Dim node As RadTreeNode = CreateNode(dbRow("Subject").ToString(), False, dbRow("TaskID").ToString())
                RadTree1.Nodes.Add(node)
                RecursivelyPopulate(dbRow, node)
            End If
        Next dbRow
    End Sub 

    Private Sub RecursivelyPopulate(ByVal dbRow As DataRow, ByVal node As RadTreeNode)
        Dim childRow As DataRow
        Dim StrikeThrough As String = ""
        Dim ExpandNode As Boolean = True
        For Each childRow In dbRow.GetChildRows("NodeRelation")
            Select Case childRow("StatusTypeID")
                Case 2
                    StrikeThrough = "ActiveTask"
                Case 3
                    StrikeThrough = "CompletedTask"
                    ExpandNode = False
                Case 4, 5
                    StrikeThrough = "ClosedTask"
                    ExpandNode = False
                Case Else
                    StrikeThrough = "InactiveTask"
                    ExpandNode = False
            End Select
            Dim childNode As RadTreeNode = CreateNode("<span class=""" & StrikeThrough & """><a href=""Task.aspx?taskid=" & childRow("TaskID").ToString() & """>" & childRow("Subject").ToString() & "</a></span>", ExpandNode, childRow("TaskID").ToString())
            node.Nodes.Add(childNode)
            RecursivelyPopulate(childRow, childNode)
            ExpandNode = True
        Next childRow
    End Sub

    Private Function CreateNode(ByVal [text] As String, ByVal expanded As Boolean, ByVal id As String) As RadTreeNode
        Dim node As New RadTreeNode([text])
        node.Expanded = expanded

        Return node
    End Function
+3  A: 

If you just need a way of enumerating the tree you can implement this as a generator, it might look strange, you're probably better of with a user defined enumerator but it's essentially the same thing.

public interface IGetChildItems<TEntity>
{
    IEnumerable<TEntity> GetChildItems();
}

public static IEnumerable<TEntity> Flatten<TEntity>(TEntity root)
    where TEntity : IGetChildItems<TEntity>
{
    var stack = new Stack<TEntity>();
    stack.Push(root);
    while (stack.Count > 0)
    {
        var item = stack.Pop();
        foreach (var child in item.GetChildItems())
        {
            stack.Push(child);
        }
        yield return item;
    }
}

The type constraint where TEntity : IGetChildItems is just to signify that you need to abstract how to descend the hierarchy. Without the above code would not compile.

This will enumerate the tree in a breadth first fashion, it will yield the parent element first then it's children, and then the children of those children. You can easily customize the above code to achieve a different behavior.

Edit:

The yield return stuff tells the compiler that it should return a value then continue. yield is a context keyword and it's only allowed inside an iterative statement. A generator is a simple way of writing a IEnumerable data source. The compiler will build a state machine from this code and create an enumerable anonymous class. Apparently the yield keyword does not exist in VB.NET. But you can still write a class which does this.

Imports System
Imports System.Collections
Imports System.Collections.Generic

Public Class HierarchyEnumerator(Of TEntity As IGetChildItems(Of TEntity))
 Implements IEnumerator(Of TEntity), IDisposable, IEnumerator

 Public Sub New(ByVal root As TEntity)
  Me.stack = New Stack(Of TEntity)
  Me.stack.Push(root)
 End Sub

 Public Sub Dispose()
 End Sub

 Public Function MoveNext() As Boolean
  Do While (Me.stack.Count > 0)
   Dim item As TEntity = Me.stack.Pop
   Dim child As TEntity
   For Each child In item.GetChildItems
    Me.stack.Push(child)
   Next
   Me.current = item
   Return True
  Loop
  Return False
 End Function

 Public Sub Reset()
  Throw New NotSupportedException
 End Sub

 Public ReadOnly Property Current() As TEntity
  Get
   Return Me.current
  End Get
 End Property

 Private ReadOnly Property System.Collections.IEnumerator.Current As Object
  Get
   Return Me.Current
  End Get
 End Property

 Private current As TEntity
 Private stack As Stack(Of TEntity)
End Class
John Leidegren
What is "yield"? I don't think VB.NET has an equivalent.
EdenMachine
I added my previous code because I couldn't make heads or tails of what you were suggesting and how that could apply to what I'm trying to accomplish. I hope that helps clarify my question.
EdenMachine