C# 从 DataTable 填充 WinForms TreeView
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/805457/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Populate WinForms TreeView from DataTable
提问by Refracted Paladin
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).
我有一个 WinForm TreeView 控件,它显示 CaseNotes 的父子关系(我知道这对你们大多数人来说毫无意义,但它可以帮助我形象化答案)。
I have a DataTable of the CaseNotes that I need to display. The Parent/Child is defined as: If the row has a ParentNoteID then it is a childNode of that note otherwise it is a rootNode. It could also be a parent note(but not a rootNode) if another row has it's ID as it's ParentNoteID.
我有一个需要显示的 CaseNotes 的数据表。父/子定义为:如果该行具有 ParentNoteID,则它是该注释的 childNode,否则它是 rootNode。如果另一行的 ID 为 ParentNoteID,则它也可能是父注释(但不是 rootNode)。
To complicate(maybe simplify) things I have the below working(mostly) code that colors the nodes alternatingly. I manually created a static collection for the treeview and it colors them fairly correctly. Now I need to dynamically populate the Nodes from my DataTable.
为了使事情复杂化(也许是简化),我有以下工作(主要是)代码,这些代码交替地为节点着色。我手动为树视图创建了一个静态集合,并且它的颜色相当正确。现在我需要从我的数据表动态填充节点。
Since I already am going thru the treeview node by node shouldn't I be able to append the data into this process somehow? Maybe I need to build the nodes first and then color as a separate routine but the Recursion Method would still apply, correct?
既然我已经在逐个节点地浏览树视图,难道我不应该能够以某种方式将数据附加到这个过程中吗?也许我需要先构建节点,然后将颜色作为单独的例程进行着色,但递归方法仍然适用,对吗?
Lets say I want to display CaseNoteID for each Node. That is returned in the DataTable and is unique.
假设我想为每个节点显示 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);
}
}
}
EDIT
编辑
My thoughts/attempts so far:
到目前为止我的想法/尝试:
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();
}
Obviously this is flawed. One it just display's ContactDate over and over. Granted it shows it the correct number of times but I would like the Value of ContactDate(which is a Column in the database and is being returned in the DataTable. Second I need to add the ChildNode Logic. A if (node.parentNode = node.CaseNoteID) blah...
显然这是有缺陷的。一个它只是一遍又一遍地显示 ContactDate。授予它显示正确的次数,但我想要 ContactDate 的值(它是数据库中的一个列,正在数据表中返回。第二,我需要添加 ChildNode 逻辑。Aif (node.parentNode = node.CaseNoteID) blah...
EDIT 2
编辑 2
So I found this link, here, and it makes it seem like I need to get my DataTable into an ArrayList. Is that correct?
所以我在这里找到了这个链接,它让我看起来好像需要将我的 DataTable 放入一个 ArrayList 中。那是对的吗?
EDIT 3
编辑 3
Okay, thanks to Cerebus this is mostly working. I just have one more question. How do I take this-->
好的,多亏了 Cerebus,这主要是有效的。我还有一个问题。我该如何处理-->
DataTable cNotesForTree = CurrentCaseNote.GetAllCNotes(Program._CurrentPerson.PersonID);
and use my returned DataTable in this? Do I just replace this -->
并在此使用我返回的数据表?我只是替换这个-->
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" });
My confusion, I think, is do I still need to do the Column.Add and Row.Adds? Also how would the DataColumn translate to my real data structure? Sorry for the very ignorant questions, the good news is I never have to ask twice.
我的困惑,我想,我还需要做 Column.Add 和 Row.Adds 吗?此外,DataColumn 将如何转换为我的真实数据结构?很抱歉这些非常无知的问题,好消息是我永远不必问两次。
EDIT 4
编辑 4
The following is providing a runtime error.
以下是提供运行时错误。
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);
}
The error is as follows --> Cannot find column [ea8428e4]Which is the first 8 digits of the correct NoteID(I have to use a Guid). Should it be looking for a column of that name?? Because I am using a Guid is there something else I need to do? I changed all the references in mine and your code to Guid...
错误如下 -->找不到 [ea8428e4] 列,这是正确 NoteID 的前 8 位数字(我必须使用 Guid)。它应该寻找那个名字的列吗??因为我使用的是 Guid,所以我还需要做其他事情吗?我将我的所有引用和您的代码更改为 Guid ...
采纳答案by Cerebrus
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!):
这应该创建一个树(对不起,我不太擅长 ASCII 艺术!):
One
|
——Two
|
————Three
|
Four
Pseudocode goes like this:
伪代码是这样的:
- Iterate through all the rows in the datatable.
- 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.
- Each complete iteration returns a node that will contain all matching childnodes with infinite nesting.
- Add the completed nodelist to the TreeView.
- 遍历数据表中的所有行。
- 对于每一行,创建一个 TreeNode 并设置它的属性。对具有与该行 ID 匹配的 ParentNodeID 的所有行递归重复该过程。
- 每次完整的迭代都会返回一个节点,该节点将包含具有无限嵌套的所有匹配子节点。
- 将完成的节点列表添加到 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:
您的场景中的问题源于“外键”指的是同一个表中的一列。这意味着当我们遍历行时,我们必须跟踪哪些行已经被解析。例如,在上表中,与第二行和第三行匹配的节点已在第一次完整迭代中添加。因此,我们不能再次添加它们。有两种方法可以跟踪这一点:
- 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) - 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
- 维护已完成的 ID 列表 (
doneNotes)。在添加每个新节点之前,请检查该列表中是否存在 noteID。这是更快的方法,通常应该是首选。(这个方法在下面的代码中被注释掉了) - 对于每次迭代,使用谓词泛型委托 (
FindNode) 搜索添加的节点列表(考虑嵌套节点),以查看要添加的节点是否存在于该列表中。这是较慢的解决方案,但我有点喜欢复杂的代码!:P
Ok, here's the tried and tested code (C# 2.0):
好的,这是久经考验的代码(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);
}
}
}
}
回答by herry
check this :
检查这个:
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
For more and full source code : How to populate treeview from datatable in vb.net
有关更多完整源代码:如何从 vb.net 中的数据表填充树视图
回答by Szybki
I've created much simplier extension method for TreeView, involving use of new simple extending class that adds two useful properties to TreeNode.
我为 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;
}
Thanks to the modiXfor his methodsto get all (nested) nodes.

