WPF TreeView从XML加载层级出现递归重复问题排查
TreeView层级递归重复问题排查与解决
问题背景
实现从XML动态填充层级结构的TreeView时,出现嵌套层级递归重复的问题。相关代码如下:
TreeView XAML
<TreeView x:Name="leftNestingTreeView" ItemsSource="{Binding Fragments, UpdateSourceTrigger=PropertyChanged}"> <TreeView.ItemTemplate> <HierarchicalDataTemplate ItemsSource="{Binding ChildFragments}" DataType="{x:Type a:FragmentModel}"> <TextBlock Text = "{Binding fragmentId}"/> </HierarchicalDataTemplate> </TreeView.ItemTemplate> </TreeView>
数据源绑定代码
Fragments = NestingTree.Values.ToList(); OnPropertyChanged(nameof(Fragments));
XML文件内容
<NestingTree> <fragment id="root_fragment" name=" n1"> <parent id="" name="" /> <childs count="1"> <child id="f1" name="frag1" /> </childs> </fragment> <fragment id="f1" name="frag1"> <parent id="root_fragment" name="n1" /> <childs count="1"> <child id="f2" name="frag2" /> </childs> </fragment> <fragment id="f2" name="frag2"> <parent id="f1" name="frag1" /> <childs count="0" /> </fragment> </NestingTree>
FragmentModel定义
public class FragmentModel : INotifyPropertyChanged { public string fragmentId { get; set; } private string fragmentName { get; set; } public string FragmentName { get { return fragmentName; } set { fragmentName = value; OnPropertyChanged(nameof(FragmentName)); } } public FragmentModel ParentFragment { get; set; } public List<FragmentModel> ChildFragments { get; set; } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "") { if (PropertyChanged != null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } }
XML数据填充代码
XDocument xmlNestingTree = XDocument.Load("nesting_tree.xml"); if (xmlNestingTree.Element("NestingTree") is not null) { int count = 0; foreach (XElement fragment in xmlNestingTree.Element("NestingTree").Elements("fragment")) { string id = fragment.Attribute("id").Value; FragmentModel f = new FragmentModel { fragmentId = id, FragmentName = fragment.Attribute("name").Value, }; nestingTree.Add(id, f); } foreach (XElement fragment in xmlNestingTree.Element("NestingTree").Elements("fragment")) { FragmentModel currentFragment = nestingTree.Values.Where(f => f.fragmentId == fragment.Attribute("id").Value).FirstOrDefault(); FragmentModel parentFragment = nestingTree.Values.Where(f => f.ChildFragments.Contains(currentFragment)).FirstOrDefault(); currentFragment.ParentFragment = parentFragment; if (Int32.TryParse(fragment.Element("childs").Attribute("count").Value, out count) && count > 0) { foreach (XElement child in fragment.Element("childs").Elements("child")) { FragmentModel currentChildFragment = nestingTree.Values.Where(f => f.fragmentId == child.Attribute("id").Value).FirstOrDefault(); currentFragment .ChildFragments .Add(currentChildFragment); } } } NestingTree = nestingTree; }
问题原因分析
- 根节点数据源错误:当前
Fragments绑定的是NestingTree的所有值,每个节点都被当作TreeView的根节点显示,而每个节点又会展开自身的子节点,导致整个层级重复出现。 - ParentFragment赋值逻辑错误:代码尝试通过查找包含当前节点的子节点集合获取父节点,但此时子节点还未设置,导致
parentFragment始终为null,完全没有建立正确的父子关系。 - ChildFragments未初始化:
FragmentModel中的ChildFragments是List类型,但未在构造函数中初始化,添加子节点时会触发空引用异常(潜在问题)。
解决方案
1. 修正FragmentModel,初始化ChildFragments
在构造函数中初始化ChildFragments,避免空引用:
public class FragmentModel : INotifyPropertyChanged { public string fragmentId { get; set; } private string fragmentName { get; set; } public string FragmentName { get { return fragmentName; } set { fragmentName = value; OnPropertyChanged(nameof(FragmentName)); } } public FragmentModel ParentFragment { get; set; } public List<FragmentModel> ChildFragments { get; set; } public event PropertyChangedEventHandler PropertyChanged; // 初始化ChildFragments public FragmentModel() { ChildFragments = new List<FragmentModel>(); } protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "") { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
2. 修正XML数据填充逻辑,正确建立父子关系
修改父节点查找逻辑,从XML的<parent>节点获取父ID,直接通过Dictionary键索引查询节点,提升性能:
XDocument xmlNestingTree = XDocument.Load("nesting_tree.xml"); if (xmlNestingTree.Element("NestingTree") is not null) { int count = 0; foreach (XElement fragment in xmlNestingTree.Element("NestingTree").Elements("fragment")) { string id = fragment.Attribute("id").Value; FragmentModel f = new FragmentModel { fragmentId = id, FragmentName = fragment.Attribute("name").Value, }; nestingTree.Add(id, f); } foreach (XElement fragment in xmlNestingTree.Element("NestingTree").Elements("fragment")) { string currentId = fragment.Attribute("id").Value; if (!nestingTree.TryGetValue(currentId, out FragmentModel currentFragment)) continue; // 从XML的parent节点获取父ID并查找父节点 string parentId = fragment.Element("parent").Attribute("id").Value; if (!string.IsNullOrEmpty(parentId) && nestingTree.TryGetValue(parentId, out FragmentModel parentFragment)) { currentFragment.ParentFragment = parentFragment; } if (Int32.TryParse(fragment.Element("childs").Attribute("count").Value, out count) && count > 0) { foreach (XElement child in fragment.Element("childs").Elements("child")) { string childId = child.Attribute("id").Value; if (nestingTree.TryGetValue(childId, out FragmentModel currentChildFragment)) { currentFragment.ChildFragments.Add(currentChildFragment); currentChildFragment.ParentFragment = currentFragment; } } } } NestingTree = nestingTree; }
3. 修正数据源,只绑定真正的根节点
根节点是XML中parent id为空的节点,修改Fragments赋值逻辑:
// 过滤出父节点为空的根节点 Fragments = NestingTree.Values.Where(f => string.IsNullOrEmpty(f.ParentFragment?.fragmentId)).ToList(); OnPropertyChanged(nameof(Fragments));
验证效果
修改后,TreeView只会以root_fragment作为根节点展开,子节点f1、f2依次嵌套,不会出现重复的层级结构。
内容的提问来源于stack exchange,提问作者TA1989
相关产品推荐
相关产品推荐

