【问题标题】:Populate WinForms TreeView from DataTable从 DataTable 填充 WinForms TreeView
【发布时间】:2010-10-22 18:30:39
【问题描述】:

我有一个 WinForm TreeView 控件,它显示 CaseNotes 的父子关系(我知道这对你们大多数人来说毫无意义,但它可以帮助我将答案可视化)。

我有一个需要显示的 CaseNotes 的数据表。 Parent/Child 定义为:如果行有 ParentNoteID,那么它是该注释的 childNode,否则它是 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);
            }
        }
    }

编辑

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

        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

所以我找到了这个链接,here,这让我觉得我需要将我的 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] 这是正确 NoteID 的前 8 位数字(我必须使用 Guid)。它应该寻找该名称的列吗?因为我使用的是 Guid,我还需要做些什么吗?我将我的所有引用和您的代码更改为 Guid...

【问题讨论】:

  • 关于“编辑 2” - 不,这不是必需的,只要您可以执行自己的逻辑来管理数据结构中的迭代。
  • 关于“编辑 3” - 我的那部分代码用于构建一个包含示例数据的示例表,以确保其正常工作。如果您的 GetAllCNotes 函数返回具有相似结构的 DataTable,那么您可以简单地删除整个 CreateData 函数。
  • 谢谢我把它拼凑起来。感谢一切!问题虽然。关于这部分 --> DataRow[] childRows = dt.Select("ParentNoteID = " + dr["NoteID"]);它给了我一个运行时错误,因为它无法通过名称找到一个 COLUMN,但它随后列出了一个值......想法?
  • 忽略以上评论。它不会让我出于某种原因删除。请参阅 EDIT 4。谢谢
  • 关于“编辑 4” - 您可能需要做一个简单的更改: dt.Select("ParentNoteID = '" + dr["NoteID"] + "'");这会在 NoteID 周围添加一个单引号,表示它是一个字符串(参见 SQL WHERE 子句语法)

标签: c# .net winforms recursion treeview


【解决方案1】:

为了尝试解决这个问题,我创建了一个示例 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 与该行 ID 匹配的所有行递归重复该过程。
  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);
      }
    }
  }
}

【讨论】:

  • 请“注意”(双关语)我已按原样保留您的 ColorNodes 功能。
  • 这可能是一个愚蠢的问题,但是在创建和使用像这样的部分类时,您通常将其放置在哪里。一个单独的类库?作为我 BLL 的一部分?谢谢!
  • 不,这是一个纯Windows窗体。
  • 是的,抱歉,我在发帖后发现了一点,但忘记回来了。
  • 感谢 Cerebrus。但是此代码代码在某些数据格式上无法完美运行,...请参阅stackoverflow.com/questions/9044104/…
【解决方案2】:

检查一下:

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

更多完整源代码:How to populate treeview from datatable in vb.net

【讨论】:

    【解决方案3】:

    我为 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 他的methods 获取所有(嵌套)节点。

    【讨论】:

      猜你喜欢
      • 2012-12-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-05-25
      相关资源
      最近更新 更多