WinForm单窗体创建多个TreeView时仅首个控件正常显示节点问题
问题描述
- 在单个WinForm窗体中创建三个TreeView对象时,仅第一个TreeView能够显示一级节点,其余TreeView均为空
- 原本计划添加到其他TreeView的TreeNode节点全部被归集到第一个TreeView中
- 仅创建单个TreeView时,节点显示逻辑完全正常
问题复现代码如下:
private void temp2() { #region Prepare data int libraryCount = 5; TreeNode[] libraries = new TreeNode[libraryCount]; for (int i = 0; i < libraryCount; i++) { TreeNode library = new TreeNode("library" + i); for (int j = 0; j < 6; j++) { TreeNode itemType = new TreeNode("ItemType" + j); library.Nodes.Add(itemType); } libraries[i] = library; } #endregion #region Create Three TreeViews TreeView tv = new TreeView(); tv.Width = 100; tv.Height = 300; tv.Top = 30; tv.Left = 30; tv.BeginUpdate(); tv.Nodes.AddRange(libraries); tv.EndUpdate(); TreeView tv2 = new TreeView(); tv2.Width = 100; tv2.Height = 300; tv2.Top = 30; tv2.Left = 130; tv2.BeginUpdate(); tv2.Nodes.AddRange(libraries); tv2.EndUpdate(); TreeView tv3 = new TreeView(); tv3.Width = 100; tv3.Height = 300; tv3.Top = 30; tv3.Left = 230; tv3.BeginUpdate(); tv3.Nodes.AddRange(libraries); tv3.EndUpdate(); #endregion #region Create form and add treeviews Form form = new Form(); form.Width = (int)(Screen.PrimaryScreen.WorkingArea.Width * 0.4); form.Height = (int)(Screen.PrimaryScreen.WorkingArea.Height * 0.5); form.Controls.AddRange(new[] { tv, tv2, tv3 }); form.Show(); #endregion }
问题产生原因
WinForm中的TreeNode是引用类型对象,每个TreeNode实例同一时间只能归属于唯一的父节点集合——这个集合要么是某个TreeView的根节点集合,要么是某个上层父TreeNode的子节点集合,不存在一个TreeNode实例同时挂载到多个TreeView的情况。
代码中仅在数据准备阶段创建了一份TreeNode实例存入libraries数组,后续三次调用AddRange给三个TreeView加节点时,传入的都是同一批实例。当框架检测到待添加的TreeNode已经有归属的父集合时,会自动先将节点从原父集合中移除,再挂载到新的集合中。受不同.NET版本的实现逻辑差异影响,最终这批节点只会固定存在于某一个TreeView中,其余两个TreeView自然为空。
修复方案
不要在多个TreeView间复用同一个TreeNode实例,给每个TreeView添加节点时,基于原始节点数据生成全新的TreeNode实例即可,最通用的实现是写递归克隆方法复制节点结构:
/// <summary> /// 递归克隆TreeNode节点,保留节点结构和文本 /// </summary> private TreeNode CloneTreeNode(TreeNode source) { TreeNode newNode = new TreeNode(source.Text); // 如果节点绑定了Tag、图标、选中状态等自定义属性,在此处同步复制 foreach (TreeNode childNode in source.Nodes) { newNode.Nodes.Add(CloneTreeNode(childNode)); } return newNode; }
添加节点时,将原节点数组替换为克隆后的新节点数组即可,三个TreeView都按该方式赋值即可正常显示全部节点:
// 给第一个TreeView加节点 tv.Nodes.AddRange(libraries.Select(node => CloneTreeNode(node)).ToArray()); // 给第二个TreeView加节点 tv2.Nodes.AddRange(libraries.Select(node => CloneTreeNode(node)).ToArray()); // 给第三个TreeView加节点 tv3.Nodes.AddRange(libraries.Select(node => CloneTreeNode(node)).ToArray());
注意:使用
Select方法做集合投影,需要在代码文件开头引入using System.Linq;命名空间。如果不想用Linq,也可以自行循环遍历原节点数组,逐个调用克隆方法生成新数组后再传入AddRange。
内容的提问来源于stack exchange,提问作者shibayan
相关产品推荐
相关产品推荐

