如何通过ViewModel从ItemsControl的ItemsSource获取自定义UserControl?
从ItemsControl中获取生成的UserControl集合
问题
我有一个ItemsControl,其ItemsSource为ObservableCollection<ViewModels>,这些ViewModel对应的DataContext是一个UserControl。现在需要从ItemsControl中获取生成的UI UserControl集合。
相关代码如下:
绑定到ObservableCollection的ItemsControl
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto" ScrollChanged="ScrollViewer_ScrollChanged" x:Name="scroll"> <StackPanel x:Name="stackPanel"> <ItemsControl ItemsSource="{Binding Interlocutor.Messages}" x:Name="itemsControl" VirtualizingPanel.ScrollUnit="Item" VirtualizingPanel.IsVirtualizing="True"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <VirtualizingStackPanel /> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemTemplate> <DataTemplate DataType="{x:Type viewmodels:MessageViewModel}"> <controls:MessageControl /> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </StackPanel> </ScrollViewer>
UserControl(MessageControl)的DataContext绑定
<UserControl x:Class="Client.Controls.MessageControl" ... xmlns:viewmodels="clr-namespace:Client.ViewModels" d:DataContext="{d:DesignInstance Type=viewmodels:MessageViewModel}" mc:Ignorable="d">
我尝试了两种方法,但均未成功,返回的是包含ViewModel的ContentPresenter对象,而非目标MessageControl:
方法一
foreach (MessageViewModel item in itemsControl.Items) { MessageControl control = (MessageControl)itemsControl.ItemContainerGenerator.ContainerFromItem(item); }
方法二
for (int i = 0; i < itemsControl.Items.Count; i++) { MessageControl element = (MessageControl)itemsControl.ItemContainerGenerator.ContainerFromIndex(i); }
解决方案
ItemContainerGenerator.ContainerFromItem/ContainerFromIndex返回的是容器元素(对于ItemsControl来说就是ContentPresenter),而DataTemplate里的MessageControl是这个容器的子元素,需要额外提取。
方法1:直接获取ContentPresenter的Content
由于你的DataTemplate直接将MessageControl作为ContentPresenter的内容,可直接读取Content属性转换:
foreach (MessageViewModel item in itemsControl.Items) { var container = itemsControl.ItemContainerGenerator.ContainerFromItem(item) as ContentPresenter; if (container != null) { var messageControl = container.Content as MessageControl; if (messageControl != null) { // 对MessageControl进行后续操作 } } }
方法2:视觉树遍历查找(通用场景)
如果DataTemplate结构更复杂,MessageControl不是直接作为容器内容,可通过视觉树递归查找目标控件:
// 通用视觉树查找方法 public static T FindVisualChild<T>(DependencyObject parent) where T : DependencyObject { for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++) { var child = VisualTreeHelper.GetChild(parent, i); if (child is T typedChild) { return typedChild; } var foundChild = FindVisualChild<T>(child); if (foundChild != null) { return foundChild; } } return null; } // 使用示例 foreach (MessageViewModel item in itemsControl.Items) { var container = itemsControl.ItemContainerGenerator.ContainerFromItem(item) as ContentPresenter; if (container != null) { var messageControl = FindVisualChild<MessageControl>(container); if (messageControl != null) { // 对MessageControl进行后续操作 } } }
注意虚拟化的影响
你的ItemsControl开启了虚拟化(VirtualizingPanel.IsVirtualizing="True"),这意味着只有当前可见的项才会生成对应的UI元素。对于不可见的项,ItemContainerGenerator会返回null。如果需要获取所有项的UI控件,可选择:
- 关闭虚拟化(将
VirtualizingPanel.IsVirtualizing设为False),但会增加内存占用 - 滚动到对应项强制生成容器,但会影响列表加载性能
内容的提问来源于stack exchange,提问作者Soerx
相关产品推荐
相关产品推荐

