Populate WinForms TreeView from DataTable

前端 未结 3 1116
深忆病人
深忆病人 2020-12-14 20:47

I have a WinForm TreeView Control that displays the Parent Child relationship of CaseNotes(I know that means nothing to most of you but it helps me visualize the answers).

3条回答
  •  太阳男子
    2020-12-14 20:58

    To attempt to solve this problem, I created a sample windows form and wrote the following code. I envisioned the datatable design as follows:

     NoteID  NoteName  ParentNoteID
       "1"    "One"        null
       "2"    "Two"        "1"
       "3"    "Three"      "2"
       "4"    "Four"       null
    ...
    

    This should create a Tree as (sorry, I'm not very good with ASCII art!):

    One
     |
     ——Two
     |
     ————Three
     |
    Four
    

    Pseudocode goes like this:

    1. Iterate through all the rows in the datatable.
    2. For each row, create a TreeNode and set it's properties. Recursively repeat the process for all rows that have a ParentNodeID matching this row's ID.
    3. Each complete iteration returns a node that will contain all matching childnodes with infinite nesting.
    4. Add the completed nodelist to the TreeView.

    The problem in your scenario arises from the fact the "foreign key" refers to a column in the same table. This means that when we iterate through the rows, we have to keep track of which rows have already been parsed. For example, in the table above, the node matching the second and third rows are already added in the first complete iteration. Therefore, we must not add them again. There are two ways to keep track of this:

    1. Maintain a list of ID's that have been done (doneNotes). Before adding each new node, check if the noteID exists in that list. This is the faster method and should normally be preferred. (this method is commented out in the code below)
    2. For each iteration, use a predicate generic delegate (FindNode) to search the list of added nodes (accounting for nested nodes) to see if the to-be added node exists in that list. This is the slower solution, but I kinda like complicated code! :P

    Ok, here's the tried and tested code (C# 2.0):


    public partial class TreeViewColor : Form
    {
      private DataTable dt;
      // Alternate way of maintaining a list of nodes that have already been added.
      //private List doneNotes;
      private static int noteID;
    
      public TreeViewColor()
      {
        InitializeComponent();
      }
    
      private void TreeViewColor_Load(object sender, EventArgs e)
      {
        CreateData();
        CreateNodes();
    
        foreach (TreeNode rootNode in treeView1.Nodes)
        {
          ColorNodes(rootNode, Color.MediumVioletRed, Color.DodgerBlue);
        }
      }
    
      private void CreateData()
      {
        dt = new DataTable("CaseNotes");
        dt.Columns.Add("NoteID", typeof(string));
        dt.Columns.Add("NoteName", typeof(string));
        DataColumn dc = new DataColumn("ParentNoteID", typeof(string));
        dc.AllowDBNull = true;
        dt.Columns.Add(dc);
    
        // Add sample data.
        dt.Rows.Add(new string[] { "1", "One", null });
        dt.Rows.Add(new string[] { "2", "Two", "1" });
        dt.Rows.Add(new string[] { "3", "Three", "2" });
        dt.Rows.Add(new string[] { "4", "Four", null });
        dt.Rows.Add(new string[] { "5", "Five", "4" });
        dt.Rows.Add(new string[] { "6", "Six", null });
        dt.Rows.Add(new string[] { "7", "Seven", null });
        dt.Rows.Add(new string[] { "8", "Eight", "7" });
        dt.Rows.Add(new string[] { "9", "Nine", "8" });
      }
    
      private void CreateNodes()
      {
        DataRow[] rows = new DataRow[dt.Rows.Count];
        dt.Rows.CopyTo(rows, 0);
        //doneNotes = new List(9);
    
        // Get the TreeView ready for node creation.
        // This isn't really needed since we're using AddRange (but it's good practice).
        treeView1.BeginUpdate();
        treeView1.Nodes.Clear();
    
        TreeNode[] nodes = RecurseRows(rows);
        treeView1.Nodes.AddRange(nodes);
    
        // Notify the TreeView to resume painting.
        treeView1.EndUpdate();
      }
    
      private TreeNode[] RecurseRows(DataRow[] rows)
      {
        List nodeList = new List();
        TreeNode node = null;
    
        foreach (DataRow dr in rows)
        {
          node = new TreeNode(dr["NoteName"].ToString());
          noteID = Convert.ToInt32(dr["NoteID"]);
    
          node.Name = noteID.ToString();
          node.ToolTipText = noteID.ToString();
    
          // This method searches the "dirty node list" for already completed nodes.
          //if (!doneNotes.Contains(doneNoteID))
    
          // This alternate method using the Find method uses a Predicate generic delegate.
          if (nodeList.Find(FindNode) == null)
          {
            DataRow[] childRows = dt.Select("ParentNoteID = " + dr["NoteID"]);
            if (childRows.Length > 0)
            {
              // Recursively call this function for all childRowsl
              TreeNode[] childNodes = RecurseRows(childRows);
    
              // Add all childnodes to this node.
              node.Nodes.AddRange(childNodes);
            }
    
            // Mark this noteID as dirty (already added).
            //doneNotes.Add(noteID);
            nodeList.Add(node);
          }
        }
    
        // Convert this List to an array so it can be added to the parent node/TreeView.
        TreeNode[] nodeArr = nodeList.ToArray();
        return nodeArr;
      }
    
      private static bool FindNode(TreeNode n)
      {
        if (n.Nodes.Count == 0)
          return n.Name == noteID.ToString();
        else
        {
          while (n.Nodes.Count > 0)
          {
            foreach (TreeNode tn in n.Nodes)
            {
              if (tn.Name == noteID.ToString())
                return true;
              else
                n = tn;
            }
          }
          return false;
        }
      }
    
      protected void ColorNodes(TreeNode root, Color firstColor, Color secondColor)
      {
        root.ForeColor = root.Index % 2 == 0 ? firstColor : secondColor;
    
        foreach (TreeNode childNode in root.Nodes)
        {
          Color nextColor = childNode.ForeColor = childNode.Index % 2 == 0 ? firstColor : secondColor;
    
          if (childNode.Nodes.Count > 0)
          {
            // alternate colors for the next node
            if (nextColor == firstColor)
              ColorNodes(childNode, secondColor, firstColor);
            else
              ColorNodes(childNode, firstColor, secondColor);
          }
        }
      }
    }
    

提交回复
热议问题