如何设置ItemsControl的ItemsSource以显示对象集合及其子对象集合
让ItemsControl依次渲染Job、Shipment、Fee控件的实现方案
嘿,我明白你的需求了——你想让ItemsControl不仅渲染Jobs集合里的Job对象,还要把每个Job下面的Shipments和Fees子集合里的元素也依次渲染出来,而且已经准备好了对应DataType的DataTemplate。那我给你两种实用的方案,你可以根据自己的布局需求选:
方案一:扁平化数据源(同层级连续渲染)
这种方法是把Job、它的Shipments、Fees全部合并到一个统一的集合里,让ItemsControl直接遍历这个扁平集合,这样所有元素会按顺序连续显示,没有层级区分。
第一步:在ViewModel里创建合并后的集合
你需要在ViewModel中新增一个集合,专门用来存放所有需要渲染的对象(Job + 对应的Shipment + Fee),还要处理集合的动态更新:
using System.Collections.ObjectModel; using System.Collections.Specialized; // 假设你的ViewModel类里已有Invoice属性 private ObservableCollection<object> _allJobRelatedItems; public ObservableCollection<object> AllJobRelatedItems { get { if (_allJobRelatedItems == null) { _allJobRelatedItems = new ObservableCollection<object>(); // 初始化时加载数据 UpdateAllJobRelatedItems(); // 监听Jobs集合的变化,自动刷新 Invoice.Jobs.CollectionChanged += Jobs_CollectionChanged; } return _allJobRelatedItems; } } private void UpdateAllJobRelatedItems() { _allJobRelatedItems.Clear(); if (Invoice?.Jobs == null) return; foreach (var job in Invoice.Jobs) { // 先添加当前Job _allJobRelatedItems.Add(job); // 添加该Job下的所有Shipment if (job.Shipments != null) { foreach (var shipment in job.Shipments) _allJobRelatedItems.Add(shipment); // 监听Shipments的变化,实时更新 job.Shipments.CollectionChanged += (s, e) => UpdateAllJobRelatedItems(); } // 添加该Job下的所有Fee if (job.Fees != null) { foreach (var fee in job.Fees) _allJobRelatedItems.Add(fee); // 监听Fees的变化,实时更新 job.Fees.CollectionChanged += (s, e) => UpdateAllJobRelatedItems(); } } } private void Jobs_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { UpdateAllJobRelatedItems(); }
第二步:修改XAML绑定
把ItemsControl的ItemsSource绑定到这个新的扁平集合,剩下的DataTemplate会自动根据DataType匹配对应的CustomControl:
<ItemsControl ItemsSource="{Binding AllJobRelatedItems}"> <ItemsControl.Resources> <DataTemplate DataType="{x:Type local:Job}"> <local:JobCustomControl /> </DataTemplate> <DataTemplate DataType="{x:Type local:Shipment}"> <local:ShipmentCustomControl /> </DataTemplate> <DataTemplate DataType="{x:Type local:Fee}"> <local:FeeCustomControl /> </DataTemplate> </ItemsControl.Resources> </ItemsControl>
方案二:嵌套ItemsControl(保持层级结构)
如果你希望每个Job控件下面紧跟着它自己的Shipments和Fees,保持清晰的层级关系,那就用嵌套ItemsControl的方式:
<ItemsControl ItemsSource="{Binding Invoice.Jobs}"> <ItemsControl.Resources> <DataTemplate DataType="{x:Type local:Job}"> <StackPanel Margin="0 0 0 10" <!-- 给Job和子项之间加个间距 --> > <!-- 先渲染当前Job的控件 --> <local:JobCustomControl DataContext="{Binding}" /> <!-- 渲染该Job下的所有Shipment --> <ItemsControl ItemsSource="{Binding Shipments}" Margin="15 0 0 0"> <!-- 缩进子项,区分层级 --> <ItemsControl.ItemTemplate> <DataTemplate DataType="{x:Type local:Shipment}"> <local:ShipmentCustomControl /> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> <!-- 渲染该Job下的所有Fee --> <ItemsControl ItemsSource="{Binding Fees}" Margin="15 0 0 0"> <ItemsControl.ItemTemplate> <DataTemplate DataType="{x:Type local:Fee}"> <local:FeeCustomControl /> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </StackPanel> </DataTemplate> <!-- 全局的DataTemplate也可以保留,方便其他地方复用 --> <DataTemplate DataType="{x:Type local:Shipment}"> <local:ShipmentCustomControl /> </DataTemplate> <DataTemplate DataType="{x:Type local:Fee}"> <local:FeeCustomControl /> </DataTemplate> </ItemsControl.Resources> </ItemsControl>
两种方案的小提示
- 方案一适合需要所有元素在视觉上是同一条连续列表的场景,比如所有Job、Shipment、Fee不分层级依次排列。
- 方案二适合需要明确区分Job和其子项层级的场景,比如每个Job作为一个“组”,下面跟着自己的Shipments和Fees。
- 如果你的集合是动态变化的(比如新增/删除Job、Shipment、Fee),方案一要记得监听所有子集合的
CollectionChanged事件来刷新扁平集合;方案二则因为直接绑定子集合,WPF会自动处理动态更新。
内容的提问来源于stack exchange,提问作者Cody W Hageman
相关产品推荐
相关产品推荐

