Заполнение WinForms TreeView от DataTable
У меня есть элемент управления TreeView WinForm, который отображает отношение родительского ребенка к CaseNotes (я знаю, это ничего не значит для большинства из вас, но это помогает мне визуализировать ответы).
У меня есть DataTable из CaseNotes, который мне нужно отобразить. Родитель/ребенок определяется как: Если строка имеет ParentNoteID, то это дочернийNode этой заметки, иначе это rootNode. Это также может быть родительская нота (но не rootNode), если в другой строке есть идентификатор в виде ParentNoteID.
Чтобы усложнить (возможно, упростить) вещи, у меня есть приведенный ниже рабочий (главным образом) код, который чередует цвета узлов. Я вручную создал статическую коллекцию для древовидной структуры, и она правильно их расписала. Теперь мне нужно динамически заполнять узлы из моего DataTable.
Так как я уже иду через treeview node на node, не должен ли я каким-то образом добавлять данные в этот процесс? Возможно, мне нужно сначала создать узлы, а затем цвет как отдельную процедуру, но метод рекурсии все равно будет применяться, правильно?
Предположим, что я хочу отображать CaseNoteID для каждого Node. Это возвращается в 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...
EDIT 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 переводится в мою реальную структуру данных? Извините за очень неосведомленные вопросы, хорошие новости - мне никогда не придется спрашивать дважды.
EDIT 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]. Это первые 8 цифр правильного NoteID (я должен использовать Guid). Должен ли он искать столбец этого имени? Потому что я использую Guid, есть ли что-то еще, что мне нужно сделать? Я изменил все ссылки в моем и вашем коде на Guid...
Ответы
Ответ 1
Чтобы попытаться решить эту проблему, я создал образец формы окна и написал следующий код. Я предположил, что тип данных выглядит следующим образом:
NoteID NoteName ParentNoteID
"1" "One" null
"2" "Two" "1"
"3" "Three" "2"
"4" "Four" null
...
Это должно создать дерево как (извините, я не очень хорошо разбираюсь в ASCII-искусстве!):
One
|
——Two
|
————Three
|
Four
Псевдокод выглядит следующим образом:
- Итерация по всем строкам в datatable.
- Для каждой строки создайте TreeNode и установите его свойства. Рекурсивно повторите процесс для всех строк, у которых есть идентификатор ParentNodeID, соответствующий этому идентификатору строки.
- Каждая полная итерация возвращает node, который будет содержать все соответствующие дочерние элементы с бесконечным вложением.
- Добавьте завершенный нодлист в TreeView.
Проблема в вашем сценарии возникает из-за того, что "внешний ключ" относится к столбцу в той же таблице. Это означает, что когда мы повторяем строки, мы должны отслеживать, какие строки уже были проанализированы. Например, в приведенной выше таблице node, соответствующая второй и третьей строкам, уже добавляется в первую полную итерацию. Поэтому мы не должны добавлять их снова. Есть два способа отслеживать это:
- Сохраните список идентификаторов, которые были выполнены (
doneNotes
). Перед добавлением каждого нового node проверьте, существует ли noteID в этом списке. Это более быстрый метод и обычно должен быть предпочтительным. (этот метод закомментирован в коде ниже)
- Для каждой итерации используйте общий разделитель предикатов (
FindNode
) для поиска списка добавленных узлов (учет вложенных узлов), чтобы увидеть, есть ли в нем список добавленных node. Это более медленное решение, но я вроде как сложный код!: P
Хорошо, здесь проверенный и проверенный код (С# 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 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);
}
}
}
}
Ответ 2
Я создал гораздо более простой метод расширения для 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 для его методов для получения всех (вложенных) узлов.
Ответ 3
проверьте это:
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
Для получения более полного и полного исходного кода: Как заполнить дерево из datatable в vb.net