Search code examples
c#datatabletreeviewconverters

How to convert a datatable to a treeview


I have a datatable similar to the one below:

            col1    col2    col3    col4    col5    col6 
name        jack    joe     will    jack    joe     will      
age         12      5       15      12      16      9 
origin      US      It      SA      Jap     Ven     Fr  

I want to get a treeview similar to the one below from this table:

jack
|_ 12
    |_ US
    |_ Jap
joe
|_ 5
|   |_ It
|_ 16
    |_ Ven     
will
|_ 15  
|   |_ SA
|_ 9
    |_ Fr

The table may have more or less number of columns and rows.

To build the treeview I have a custom Node class as below:

public class Node : PropertyChangedBase
{
    private ObservableCollection<Node> mChildren;

    // Add all of the properties of a node here. In this example,
    // all we have is a name and whether we are expanded.
    public string Name
    {
        get { return _name; }
        set
        {
            if (_name != value)
            {
                _name = value;
                NotifyOfPropertyChange(() => Name);
            }
        }
    }
    private string _name;

    public bool IsExpanded
    {
        get { return _isExpanded; }
        set
        {
            if (_isExpanded != value)
            {
                _isExpanded = value;
                NotifyOfPropertyChange(() => IsExpanded);
            }
        }
    }
    private bool _isExpanded;

    // Children are required to use this in a TreeView
    public IList<Node> Children { get { return mChildren; } }

    // Parent is optional. Include if you need to climb the tree
    // from code. Not usually necessary.
    public Node Parent { get; private set; }

    public Node(Node parent = null)
    {
        mChildren = new ObservableCollection<Node>();
        IsExpanded = true;
        Parent = parent;
    }

     
}

and this is how I add nodes to the tree:

mRootNodes = new ObservableCollection<Node>();

// Test data for example purposes
Node root = new Node() { Name = "Root" };
Node a = new Node(root) { Name = "Node A" };
root.Children.Add(a);
Node b = new Node(root) { Name = "Node B" };
root.Children.Add(b);
Node c = new Node(b) { Name = "Node C" };
b.Children.Add(c);
Node d = new Node(b) { Name = "Node D" };
b.Children.Add(d);
Node e = new Node(root) { Name = "Node E" };
root.Children.Add(e);
mRootNodes.Add(root);

For which I get the below tree:

root
  Node A
  Node B    
    Node C
    Node D
  Node E

How can I do this in C#?

Any comment is appreciated.


Solution

  • Assuming that:

    1. each column data represents new (or existing) node

    for example:

    if jack in row[0] => col1 and col4 refers to the same node

    12 in row[1] => col1 and col4 refers to the same node (of Jack)

    and so on...

    1. each row in the datatable represents another level of node...

    then you need to loop through the columns and rows to convert data into list of Node's.

    Note: In the below example i'm using List<Node> instead of ObservableCollection<Node>(), but this should give you an idea...

    This is a Node class definition:

    class Node
    {
        public string Name{get; set;}
        public List<Node> Children {get; set;}
    }
    

    Note: i removed Parent member due to circular reference.

    Usage (LinqPad):

    DataTable dt = new DataTable();
    dt.Columns.Add(new DataColumn("prop", typeof(string)));
    dt.Columns.AddRange(Enumerable.Range(1,6).Select(x=>new DataColumn($"col{x}", typeof(string))).ToArray());
    dt.Rows.Add(new object[]{"name", "jack", "joe", "will", "jack", "joe", "will"});
    dt.Rows.Add(new object[]{"age", "12", "5", "15", "12", "16", "9"});
    dt.Rows.Add(new object[]{"origin", "US", "It", "SA", "Jap", "Ven", "Fr"});
    
    List<Node> nodes = new List<Node>();
    for(int c=1; c<dt.Columns.Count; c++)
    {
        Node parent = null;
        Node current = null;
        for(int r=0; r<dt.Rows.Count; r++)
        {
            DataRow dr = dt.Rows[r];
            string name = Convert.ToString(dr[c]);
            if(parent==null)
            {
                parent = new Node(){Name = name, Children= new List<Node>()};
                current = nodes.Where(x=>x.Name==parent.Name).SingleOrDefault();
                if(current==null)
                    nodes.Add(parent);
                else
                    parent = current;
            }
            else
            {
                current = new Node(){Name = name, Children= new List<Node>()};
                if(parent.Children.Where(x=>x.Name==current.Name).SingleOrDefault()==null)
                    parent.Children.Add(current);
                else
                    current = parent.Children.Where(x=>x.Name==current.Name).SingleOrDefault();
                parent = current;
            }
        }
    }
    nodes.Dump();
    

    In a real world, i'd suggest to create helper class...