从DataTable填充WinForms TreeView

我有一个WinForm TreeView控件,显示CaseNotes的父子关系(我知道这对大多数人来说没什么意义,但它可以帮助我看到答案)。

我有一个我需要显示的CaseNotes的DataTable。 父/子被定义为:如果行具有ParentNoteID,则它是该注释的childNode,否则它是rootNode。 它也可以是父笔记(但不是rootNode),如果另一行有它的ID,因为它是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逻辑。一个if (node.parentNode = node.CaseNoteID) blah...

编辑2

所以我在这里找到了这个链接,这让我觉得我需要把我的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 ……

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

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

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

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

伪代码是这样的:

  1. 遍历数据表中的所有行。
  2. 对于每一行,创建一个TreeNode并设置它的属性。 递归地重复具有与此行的ID匹配的ParentNodeID的所有行的过程。
  3. 每个完整的迭代返回一个节点,该节点将包含具有无限嵌套的所有匹配的子节点。
  4. 将完成的节点列表添加到TreeView。

您的方案中的问题源于“外键”引用同一表中的列的事实。 这意味着当我们遍历行时,我们必须跟踪已经解析的行。 例如,在上表中,匹配第二行和第三行的节点已在第一次完整迭代中添加。 因此,我们不能再添加它们。 有两种方法可以跟踪这个:

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

好的,这是久经考验的代码(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); } } } } 

我为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()) { foreach (IdNode newparent in GetAllNodes(treeView1).OfType()) { if (newparent.Id.Equals(idnode.ParentId)) { treeView1.Nodes.Remove(idnode); newparent.Nodes.Add(idnode); break; } } } treeView1.EndUpdate(); } public static List GetAllNodes(this TreeView tv) { List result = new List(); foreach (TreeNode child in tv.Nodes) { result.AddRange(GetAllNodes(child)); } return result; } public static List GetAllNodes(this TreeNode tn) { List result = new List(); result.Add(tn); foreach (TreeNode child in tn.Nodes) { result.AddRange(GetAllNodes(child)); } return result; } 

感谢modiX用于获取所有(嵌套)节点的方法。

检查一下:

 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 

有关更多完整的源代码: 如何从vb.net中的数据表填充树视图