Вопрос

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

У меня есть таблица данных CaseNotes, которую мне нужно отобразить.Родитель/Ребенок определяется как:Если строка имеет ParentNoteID, то это дочерний узел этой заметки, в противном случае — корневой узел.Это также может быть родительская заметка (но не корневой узел), если другая строка имеет такой же идентификатор, как 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);
            }
        }
    }

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

Мои мысли/попытки на данный момент:

        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.А 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" });

Я думаю, что меня смущает: нужно ли мне еще делать 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...

Это было полезно?

Решение

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

 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) для поиска в списке добавленных узлов (с учетом вложенных узлов), чтобы увидеть, существует ли добавляемый узел в этом списке.Это более медленное решение, но мне нравится сложный код!:П

Хорошо, вот проверенный код (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;
    }

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

Проверь это :

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

Для получения дополнительной информации и полного исходного кода: Как заполнить древовидную структуру из таблицы данных в vb.net

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top