从DataTable填充WinForms TreeView

Ref*_*din 14 .net c# treeview recursion winforms

我有一个WinForm TreeView控件,显示CaseNotes的Parent Child关系(我知道这对大多数人来说没有任何意义,但它可以帮助我查看答案).

我有一个我需要显示的CaseNotes的DataTable.父/子被定义为:如果行具有ParentNoteID,则它是该注释的childNode,否则它是rootNode.它也可以是父笔记(但不是rootNode),如果另一行有它的ID,因为它是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);
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

编辑

到目前为止我的想法/尝试:

        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();
    }
Run Code Online (Sandbox Code Playgroud)

显然这是有缺陷的.一个只是一遍又一遍地显示ContactDate.虽然它显示了正确的次数,但我想要ContactDate的值(它是数据库中的一个列,并在DataTable中返回.其次我需要添加ChildNode逻辑.if (node.parentNode = node.CaseNoteID) blah...

编辑2

所以我在这里找到了这个链接,这让我觉得我需要把我的DataTable变成一个ArrayList.那是对的吗?

编辑3

好的,多亏了Cerebus,这大部分都在工作.我还有一个问题.我该怎么做 - >

DataTable cNotesForTree = CurrentCaseNote.GetAllCNotes(Program._CurrentPerson.PersonID);
Run Code Online (Sandbox Code Playgroud)

并在此使用我返回的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" });
Run Code Online (Sandbox Code Playgroud)

我认为,我的困惑是,我还需要做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);
  }
Run Code Online (Sandbox Code Playgroud)

错误如下 - > 找不到列[ea8428e4]这是正确NoteID的前8位数字(我必须使用Guid).它应该寻找那个名称的列?因为我使用的是Guid还有其他我需要做的事情吗?我将我的所有引用和你的代码改为Guid ......

Cer*_*rus 12

为了尝试解决这个问题,我创建了一个示例窗体并编写了以下代码.我设想的数据表设计如下:

 NoteID  NoteName  ParentNoteID
   "1"    "One"        null
   "2"    "Two"        "1"
   "3"    "Three"      "2"
   "4"    "Four"       null
...
Run Code Online (Sandbox Code Playgroud)

这应该创建一个树(抱歉,我对ASCII艺术不是很好!):

One
 |
 ——Two
 |
 ————Three
 |
Four
Run Code Online (Sandbox Code Playgroud)

伪代码是这样的:

  1. 遍历数据表中的所有行.
  2. 对于每一行,创建一个TreeNode并设置它的属性.递归地重复具有与此行的ID匹配的ParentNodeID的所有行的过程.
  3. 每个完整的迭代返回一个节点,该节点将包含具有无限嵌套的所有匹配的子节点.
  4. 将完成的节点列表添加到TreeView.

您的方案中的问题源于"外键"引用同一表中的列的事实.这意味着当我们遍历行时,我们必须跟踪哪些行已经被解析.例如,在上表中,匹配第二行和第三行的节点已在第一次完整迭代中添加.因此,我们不能再添加它们.有两种方法可以跟踪这个:

  1. 维护已完成的ID列表(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);
      }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)