我有一个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...
所以我找到了这个链接,here,这让我觉得我需要将我的DataTable放到ArrayList中。这是对的吗?
好的,感谢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如何转换为我的真实数据结构?对于非常无知的问题感到抱歉,好消息是我从来没有问过两次。
以下是提供运行时错误。
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 ......
答案 0 :(得分:12)
为了尝试解决这个问题,我创建了一个示例窗体并编写了以下代码。我设想数据表设计如下:
NoteID NoteName ParentNoteID
"1" "One" null
"2" "Two" "1"
"3" "Three" "2"
"4" "Four" null
...
这应该创建一个树(抱歉,我对ASCII艺术并不是很好!):
One
|
——Two
|
————Three
|
Four
Pseudocode是这样的:
您的方案中的问题源于“外键”引用同一表中的列的事实。这意味着当我们遍历行时,我们必须跟踪哪些行已经被解析。例如,在上表中,匹配第二行和第三行的节点已在第一次完整迭代中添加。因此,我们不能再添加它们。有两种方法可以跟踪这一点:
doneNotes
)。在添加每个新节点之前,请检查该列表中是否存在noteID。这是更快的方法,通常应该是首选方法。 (此方法在下面的代码中注释掉)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<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;
}
答案 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