如何使用递归构建自定义控件?基于ViewModel的复杂控件开发问询
递归构建自定义控件的ViewModel实现方案
看起来你想要用递归逻辑在ViewModel里驱动复杂自定义控件的层级构建,刚好我之前做过类似的场景,给你一套完整的实现方案参考:
首先明确核心场景:基于Publisher(包含子Publisher集合和子Developer集合)和Developer两个类,通过ViewModel的递归逻辑,让UI自动渲染出多层级的控件结构。
第一步:完善支持递归的Model/ViewModel类
先把Publisher和Developer类补全,确保它们支持属性变更通知,并且Publisher的子集合能触发UI更新:
using System.Collections.ObjectModel; using System.ComponentModel; using System.Runtime.CompilerServices; public class Publisher : INotifyPropertyChanged { private ObservableCollection<Publisher> _subPublishers; private ObservableCollection<Developer> _childDevelopers; private string _name; public Publisher() { _subPublishers = new ObservableCollection<Publisher>(); _childDevelopers = new ObservableCollection<Developer>(); // 绑定集合变更事件,确保子集合变化时UI能感知 _subPublishers.CollectionChanged += OnCollectionChanged; _childDevelopers.CollectionChanged += OnCollectionChanged; } public string Name { get => _name; set { _name = value; OnPropertyChanged(); } } public ObservableCollection<Publisher> SubPublishers { get => _subPublishers; set { if (_subPublishers != value) { _subPublishers.CollectionChanged -= OnCollectionChanged; _subPublishers = value; _subPublishers.CollectionChanged += OnCollectionChanged; OnPropertyChanged(); } } } public ObservableCollection<Developer> ChildDevelopers { get => _childDevelopers; set { if (_childDevelopers != value) { _childDevelopers.CollectionChanged -= OnCollectionChanged; _childDevelopers = value; _childDevelopers.CollectionChanged += OnCollectionChanged; OnPropertyChanged(); } } } private void OnCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { // 集合变化时通知UI刷新相关属性 OnPropertyChanged(nameof(SubPublishers)); OnPropertyChanged(nameof(ChildDevelopers)); } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } public class Developer : INotifyPropertyChanged { private string _name; private string _skill; public string Name { get => _name; set { _name = value; OnPropertyChanged(); } } public string Skill { get => _skill; set { _skill = value; OnPropertyChanged(); } } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
第二步:用递归逻辑构建层级数据
在主ViewModel里,编写递归方法生成多层级的Publisher结构,驱动UI的递归渲染:
public class MainViewModel : INotifyPropertyChanged { private Publisher _rootPublisher; public Publisher RootPublisher { get => _rootPublisher; set { _rootPublisher = value; OnPropertyChanged(); } } public MainViewModel() { // 初始化时自动构建递归层级数据 RootPublisher = BuildRecursivePublisherHierarchy(); } // 递归构建Publisher层级的核心方法,注意设置终止条件避免无限递归 private Publisher BuildRecursivePublisherHierarchy(int currentDepth = 0, int maxDepth = 2) { var publisher = new Publisher { Name = $"Publisher Level {currentDepth}" }; // 给当前Publisher添加子开发者 for (int i = 0; i < 2; i++) { publisher.ChildDevelopers.Add(new Developer { Name = $"Dev {i} under Publisher {currentDepth}", Skill = currentDepth == 0 ? "Senior Engineer" : "Junior Engineer" }); } // 递归添加子Publisher,直到达到最大深度 if (currentDepth < maxDepth) { for (int i = 0; i < 1; i++) { publisher.SubPublishers.Add(BuildRecursivePublisherHierarchy(currentDepth + 1, maxDepth)); } } return publisher; } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
第三步:自定义控件的递归UI实现
在XAML里用HierarchicalDataTemplate实现递归渲染,不需要手动写嵌套逻辑,模板会自动处理层级:
<UserControl x:Class="YourNamespace.RecursivePublisherControl" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:YourNamespace"> <UserControl.Resources> <!-- 开发者项的渲染模板 --> <DataTemplate DataType="{x:Type local:Developer}"> <StackPanel Orientation="Horizontal" Margin="4"> <TextBlock Text="👨💻 " /> <StackPanel> <TextBlock Text="{Binding Name}" FontWeight="Bold"/> <TextBlock Text="{Binding Skill}" FontStyle="Italic" Foreground="#666"/> </StackPanel> </StackPanel> </DataTemplate> <!-- Publisher的递归模板,自动渲染子Publisher --> <HierarchicalDataTemplate DataType="{x:Type local:Publisher}" ItemsSource="{Binding SubPublishers}"> <Expander Header="{Binding Name}" Margin="2" BorderThickness="1" BorderBrush="#EEE"> <StackPanel> <!-- 渲染当前Publisher下的所有开发者 --> <ItemsControl ItemsSource="{Binding ChildDevelopers}" Margin="8,0"/> <!-- 递归渲染子Publisher,HierarchicalDataTemplate会自动复用模板 --> <ItemsControl ItemsSource="{Binding SubPublishers}" Margin="8,0"/> </StackPanel> </Expander> </HierarchicalDataTemplate> </UserControl.Resources> <!-- 控件主内容,绑定根Publisher的子集合 --> <ItemsControl ItemsSource="{Binding RootPublisher.SubPublishers}"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <StackPanel/> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> </ItemsControl> </UserControl>
几个关键注意点
- 递归终止条件:一定要在递归方法里设置明确的终止条件(比如示例里的
maxDepth),不然会触发无限递归导致栈溢出。 - 性能优化:如果层级很深或者数据量很大,记得把
ItemsPanel换成VirtualizingStackPanel,开启UI虚拟化提升渲染性能。 - 变更通知:所有属性和集合都要正确实现
INotifyPropertyChanged和ObservableCollection,不然UI不会实时响应数据变化。
内容的提问来源于stack exchange,提问作者user9145305
相关产品推荐
相关产品推荐

