从DataTable填充WinForms TreeView

时间:2009-04-30 05:31:34

标签: c# .net winforms recursion treeview

我有一个WinForm TreeView控件,它显示了CaseNotes的Parent Child关系(我知道这对大多数人来说没有任何意义,但它可以帮助我查看答案)。

我有一个需要显示的CaseNotes的DataTable。父/子被定义为:如果行具有ParentNoteID,则它是该注释的childNode,否则它是rootNode。如果另一行有ID,那么它也可以是父笔记(但不是rootNode),因为它是ParentNoteID。

为了使事情变得复杂(可能是简化),我有以下工作(大部分)代码,它们交替地为节点着色。我手动为树视图创建了一个静态集合,它可以非常正确地为它们着色。现在我需要从我的DataTable中动态填充节点。

由于我已逐步通过树视图逐节点,我不能以某种方式将数据附加到此过程中吗?也许我需要先构建节点,然后将颜色作为一个单独的例程,但递归方法仍然适用,对吗?

假设我想为每个节点显示CaseNoteID。这是在DataTable中返回的,并且是唯一的。

foreach (TreeNode rootNode in tvwCaseNotes.Nodes)
        {
            ColorNodes(rootNode, Color.MediumVioletRed, Color.DodgerBlue);

        }
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);
            }
        }
    }

修改

到目前为止我的想法/尝试:

        public void BuildSummaryView()
    {
        tvwCaseNotes.Nodes.Clear();

        DataTable cNotesForTree = CurrentCaseNote.GetAllCNotes(Program._CurrentPerson.PersonID);
        foreach (var cNote in cNotesForTree.Rows)
        {

            tvwCaseNotes.Nodes.Add(new TreeNode("ContactDate"));
        }
        FormPaint();
    }

显然这是有缺陷的。一个只是一遍又一遍地显示ContactDate。虽然它显示了正确的次数,但我想要ContactDate的值(它是数据库中的一个列,并在DataTable中返回。其次我需要添加ChildNode逻辑.A if (node.parentNode = node.CaseNoteID) blah...

编辑2

所以我找到了这个链接,here,这让我觉得我需要将我的DataTable放到ArrayList中。这是对的吗?

编辑3

好的,感谢Cerebus,这大部分都在工作。我还有一个问题。我该怎么做 - >

DataTable cNotesForTree = CurrentCaseNote.GetAllCNotes(Program._CurrentPerson.PersonID);

并在此使用我返回的DataTable?我只是替换它 - >

    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" });

我认为我的困惑是,我还需要做Column.Add和Row.Adds吗? DataColumn如何转换为我的真实数据结构?对于非常无知的问题感到抱歉,好消息是我从来没有问过两次。

编辑4

以下是提供运行时错误。

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);
  }

错误如下 - > 找不到列[ea8428e4] 这是正确NoteID的前8位数字(我必须使用Guid)。它应该寻找那个名称的列?因为我使用的是Guid还有其他我需要做的事情吗?我将我的所有引用和你的代码改为Guid ......

3 个答案:

答案 0 :(得分:12)

为了尝试解决这个问题,我创建了一个示例窗体并编写了以下代码。我设想数据表设计如下:

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

这应该创建一个树(抱歉,我对ASCII艺术并不是很好!):

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

Pseudocode是这样的:

  1. 遍历数据表中的所有行。
  2. 对于每一行,创建一个TreeNode并设置它的属性。递归地重复具有与此行的ID匹配的ParentNodeID的所有行的过程。
  3. 每次完整迭代都会返回一个节点,该节点将包含具有无限嵌套的所有匹配子节点。
  4. 将完成的节点列表添加到TreeView。
  5. 您的方案中的问题源于“外键”引用同一表中的列的事实。这意味着当我们遍历行时,我们必须跟踪哪些行已经被解析。例如,在上表中,匹配第二行和第三行的节点已在第一次完整迭代中添加。因此,我们不能再添加它们。有两种方法可以跟踪这一点:

    1. 维护已完成的ID列表(doneNotes)。在添加每个新节点之前,请检查该列表中是否存在noteID。这是更快的方法,通常应该是首选方法。 (此方法在下面的代码中注释掉
    2. 对于每次迭代,使用谓词通用委托(FindNode)来搜索添加的节点列表(计算嵌套节点),以查看该列表中是否存在要添加的节点。这是较慢的解决方案,但我有点像复杂的代码! :P
    3. 好的,这是久经考验的代码(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<int> 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<int>(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<TreeNode> nodeList = new List<TreeNode>();
          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<TreeNode> 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);
            }
          }
        }
      }
      

答案 1 :(得分:1)

我为TreeView创建了更简单的扩展方法,包括使用新的简单扩展类,为TreeNode添加了两个有用的属性。

    internal class IdNode : TreeNode
    {
        public object Id { get; set; }
        public object ParentId { get; set; }
    }

    public static void PopulateNodes(this TreeView treeView1, DataTable dataTable, string name, string id, string parentId)
    {
        treeView1.BeginUpdate();
        foreach (DataRow row in dataTable.Rows)
        {
            treeView1.Nodes.Add(new IdNode() { Name = row[name].ToString(), Text = row[name].ToString(), Id = row[id], ParentId = row[parentId], Tag = row });
        }
        foreach (IdNode idnode in GetAllNodes(treeView1).OfType<IdNode>())
        {
            foreach (IdNode newparent in GetAllNodes(treeView1).OfType<IdNode>())
            {
                if (newparent.Id.Equals(idnode.ParentId))
                {
                    treeView1.Nodes.Remove(idnode);
                    newparent.Nodes.Add(idnode);
                    break;
                }
            }
        }
        treeView1.EndUpdate();
    }

    public static List<TreeNode> GetAllNodes(this TreeView tv)
    {
        List<TreeNode> result = new List<TreeNode>();
        foreach (TreeNode child in tv.Nodes)
        {
            result.AddRange(GetAllNodes(child));
        }
        return result;
    }
    public static List<TreeNode> GetAllNodes(this TreeNode tn)
    {
        List<TreeNode> result = new List<TreeNode>();
        result.Add(tn);
        foreach (TreeNode child in tn.Nodes)
        {
            result.AddRange(GetAllNodes(child));
        }
        return result;
    }

感谢modiX methods获取所有(嵌套)节点。

答案 2 :(得分:-1)

检查一下:

Public Sub BuildTree(ByVal dt As DataTable, ByVal trv As TreeView, ByVal expandAll As [Boolean])
    ' Clear the TreeView if there are another datas in this TreeView
    trv.Nodes.Clear()
    Dim node As TreeNode
    Dim subNode As TreeNode
    For Each row As DataRow In dt.Rows
        'search in the treeview if any country is already present
        node = Searchnode(row.Item(0).ToString(), trv)
        If node IsNot Nothing Then
           'Country is already present
            subNode = New TreeNode(row.Item(1).ToString())
            'Add cities to country
            node.Nodes.Add(subNode)
        Else
            node = New TreeNode(row.Item(0).ToString())
            subNode = New TreeNode(row.Item(1).ToString())
            'Add cities to country
            node.Nodes.Add(subNode)
            trv.Nodes.Add(node)
        End If
    Next
    If expandAll Then
        ' Expand the TreeView
        trv.ExpandAll()
    End If
End Sub

有关更多完整的源代码:How to populate treeview from datatable in vb.net