Заполнить WinForms TreeView из DataTable



У меня есть элемент управления WinForm TreeView, который отображает родительско-дочерние отношения CaseNotes (я знаю, что это ничего не значит для большинства из вас, но это помогает мне визуализировать ответы).



У меня есть DataTable CaseNotes, которые мне нужно отобразить. Родитель / потомок определяется следующим образом: если строка имеет ParentNoteID, то это childNode этой заметки, в противном случае это rootNode. Это также может быть родительская заметка (но не rootNode), если другая строка имеет свой идентификатор, так как это ParentNoteID.



К усложнить (может быть, упростить) вещи у меня есть ниже рабочий (в основном) код, который окрашивает узлы попеременно. Я вручную создал статическую коллекцию для treeview, и она раскрашивает их довольно правильно. Теперь мне нужно динамически заполнить узлы из моей DataTable.



Поскольку я уже иду через узел treeview за узлом, не должен ли я каким-то образом добавлять данные в этот процесс? Может быть, мне нужно сначала построить узлы, а затем раскрасить как отдельную процедуру, но рекурсия Метод все равно применим, верно?



Допустим, я хочу отобразить 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



Мои мысли / попытки до сих пор:



        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(который является столбцом в базе данных и возвращается в DataTable. Во-вторых, мне нужно добавить: логика ChildNode. A 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" });


Мое замешательство, я думаю, заключается в том, нужно ли мне все еще делать колонку.Добавлять и Ряд.Добавляет? Кроме того, как 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...

830   3  

3 ответов:

Чтобы попытаться решить эту проблему, я создал образец формы windows и написал следующий код. Я представлял себе конструкцию datatable следующим образом:

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

Это должно создать дерево как (извините, я не очень хорошо разбираюсь в ASCII-искусстве!):

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

Псевдокод звучит так:

  1. перебирайте все строки в datatable.
  2. для каждой строки создайте дерево и задайте его свойства. Рекурсивно повторите процесс для всех строк, имеющих ParentNodeID совпадение с идентификатором этой строки.
  3. каждая полная итерация возвращает узел, который будет содержать все соответствующие дочерние узлы с бесконечной вложенностью.
  4. добавьте завершенный список узлов в TreeView.

Проблема в вашем сценарии возникает из-за того, что "внешний ключ" ссылается на столбец в той же таблице. Это означает, что при итерации по строкам мы должны отслеживать, какие строки уже были проанализированы. Например, в приведенной выше таблице узел, соответствующий второму и третьи строки уже добавлены в первой полной итерации. Поэтому мы не должны добавлять их снова. Есть два способа отслеживать это:

  1. ведение списка идентификаторов, которые были сделаны (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<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);
      }
    }
  }
}

Я создал гораздо более простой метод расширения для 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;
    }

Благодарямодиксу для егометодов , чтобы получить все (вложенные) узлы.

Проверьте это :

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

Для получения более полного исходного кода: Как заполнить treeview из datatable в vb.net

Comments

    Ничего не найдено.