Заполнить WinForms TreeView из DataTable - PullRequest
14 голосов
/ 30 апреля 2009

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

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

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

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

Допустим, я хочу отобразить 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 снова и снова. Конечно, это показывает правильное количество раз, но я хотел бы, чтобы значение ContactDate (которое является столбцом в базе данных и возвращалось в DataTable. Во-вторых, мне нужно добавить логику ChildNode. A if (node.parentNode = node.CaseNoteID) blah...

РЕДАКТИРОВАТЬ 2

Итак, я нашел эту ссылку, здесь , и кажется, что мне нужно поместить DataTable в ArrayList. Это правильно?

РЕДАКТИРОВАТЬ 3

Хорошо, благодаря Серебусу, это в основном работает. У меня просто есть еще один вопрос. Как мне это принять ->

DataTable cNotesForTree = CurrentCaseNote.GetAllCNotes(Program._CurrentPerson.PersonID);

и использовать мою возвращенную таблицу данных в этом? Должен ли я просто заменить это ->

    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] Это первые 8 цифр правильного NoteID (я должен использовать Guid). Должен ли он искать столбец с таким именем? Поскольку я использую Guid, есть ли что-то еще, что мне нужно сделать? Я изменил все ссылки в моем и ваш код на Guid ...

Ответы [ 3 ]

12 голосов
/ 01 мая 2009

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

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

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

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

Псевдокод выглядит так:

  1. Итерация по всем строкам таблицы данных.
  2. Для каждой строки создайте TreeNode и установите его свойства. Рекурсивно повторите процесс для всех строк, у которых 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);
      }
    }
  }
}

1 голос
/ 21 июня 2016

Я создал гораздо более простой метод расширения для 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 за его методы для получения всех (вложенных) узлов.

0 голосов
/ 04 октября 2011

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...