多重内容模式Items
WPF 中的多项内容模型(Items Model):列表类控件的核心逻辑
WPF 的多项内容模型(Items Model) 是专门为 “需要展示多个子项” 的控件设计的核心规则(对应你提到的
多项内容模型允许控件容纳一个元素集合(而非单个元素),并通过统一的规则渲染、布局、交互这些子项,是 WPF 实现灵活列表展示的核心。
Items),是 ListBox、ComboBox、ListView、Menu 等列表类控件的底层基础。简单来说:多项内容模型允许控件容纳一个元素集合(而非单个元素),并通过统一的规则渲染、布局、交互这些子项,是 WPF 实现灵活列表展示的核心。
一、多项内容模型的核心定位
1. 为什么需要多项内容模型?
单一内容模型(ContentControl)只能放一个元素(如 Button 的 Content),但实际开发中需要展示 “列表、菜单、下拉选项” 等多元素场景 —— 多项内容模型就是为解决这个问题而生,它提供了:
- 批量管理子项的能力(添加 / 删除 / 绑定数据源);
- 统一渲染每个子项的模板;
- 自定义子项布局的方式;
- 子项的选中、交互等通用逻辑。
2. 核心基类:ItemsControl
所有支持多项内容模型的控件,都直接 / 间接继承自
常见的 ItemsControl 子类:
ItemsControl(WPF 的核心基类),它定义了多项内容模型的所有核心属性和行为。常见的 ItemsControl 子类:
| 控件名 | 典型用途 | 核心特性 |
|---|---|---|
| ListBox | 可选择的列表 | 支持单选 / 多选、选中项绑定 |
| ComboBox | 下拉选择框 | 折叠展示、下拉面板 |
| ListView | 可自定义视图的列表 | 支持 GridView、卡片视图 |
| Menu/ContextMenu | 菜单 / 右键菜单 | 层级菜单、快捷键 |
| TabControl | 标签页 | 切换不同 Content 的标签 |
| ItemsControl | 基础多内容控件 | 仅展示列表,无选中逻辑 |
二、多项内容模型的核心属性(必掌握)
ItemsControl 定义了多项内容模型的核心属性,这些属性是使用 / 自定义多内容控件的关键:
| 属性名 | 作用 | 优先级 / 使用建议 |
|---|---|---|
Items |
手动添加的子项集合(UIElement 类型) | 静态场景用(如固定下拉选项),优先级低于 ItemsSource |
ItemsSource |
绑定的数据源(IEnumerable 类型) | 动态场景用(MVVM 核心),优先级高于 Items |
ItemTemplate |
单个子项的渲染模板(DataTemplate) | 统一定义所有子项的外观 |
ItemTemplateSelector |
模板选择器(根据子项类型动态选模板) | 多类型子项场景(如混合文本 + 图片项) |
ItemsPanel |
子项的布局容器(ItemsPanelTemplate) | 自定义子项排列方式(横向 / 网格 / 瀑布流) |
ItemContainerStyle |
子项容器的样式(如 ListBoxItem 的样式) | 自定义子项的容器外观(选中态、hover) |
DisplayMemberPath |
数据源对象的属性名(快速显示文本) | 简单场景替代 ItemTemplate(仅显示文本) |
SelectedItem |
当前选中项(可选控件特有,如 ListBox) | 绑定选中数据,MVVM 核心 |
SelectedIndex |
当前选中项索引(可选控件特有) | 按索引控制选中 |
关键规则:ItemsSource vs Items
- 互斥性:不要同时使用
Items和ItemsSource——ItemsSource 优先级更高,设置后 Items 会被清空; - MVVM 原则:优先使用
ItemsSource绑定数据源(如ObservableCollection<T>),而非手动操作Items(符合 MVVM,解耦 UI 和数据)。
三、多项内容模型的核心渲染逻辑
多项内容模型的渲染流程是理解其底层的关键,核心分为 3 步:

核心组件:ItemsPresenter
和单一内容模型的
所有 ItemsControl 的默认模板中,都包含 ItemsPresenter,自定义模板时必须保留它,否则子项无法显示。
ContentPresenter对应,ItemsPresenter是多项内容模型的 “占位符”—— 它必须写在 ItemsControl 的模板中,负责将 “布局好的子项” 渲染到控件模板的指定位置。所有 ItemsControl 的默认模板中,都包含 ItemsPresenter,自定义模板时必须保留它,否则子项无法显示。
四、多项内容模型的实战场景
场景 1:静态添加子项(简单场景)
适用于子项固定的场景(如下拉框的固定选项),直接在 XAML 中添加子项:
1 <!-- ComboBox静态添加项 --> 2 <ComboBox Width="150" Height="30" SelectedIndex="0"> 3 <ComboBoxItem Content="北京" /> 4 <ComboBoxItem Content="上海" /> 5 <ComboBoxItem Content="广州" /> 6 <!-- 也可直接写文本(WPF自动包装为ComboBoxItem) --> 7 <TextBlock Text="深圳" /> 8 <string>成都</string> 9 </ComboBox> 10 11 <!-- ListBox静态添加复杂项 --> 12 <ListBox Width="200" Height="120"> 13 <StackPanel Orientation="Horizontal"> 14 <materialDesign:PackIcon Kind="User" Width="16" Height="16"/> 15 <TextBlock Text="张三" Margin="5 0 0 0"/> 16 </StackPanel> 17 <StackPanel Orientation="Horizontal"> 18 <materialDesign:PackIcon Kind="User" Width="16" Height="16"/> 19 <TextBlock Text="李四" Margin="5 0 0 0"/> 20 </StackPanel> 21 </ListBox>
场景 2:动态绑定数据源(MVVM 核心)
这是实际开发中最常用的场景,通过
ItemsSource绑定数据源,ItemTemplate定义子项外观:步骤 1:定义数据模型和 ViewModel
1 // 数据模型 2 public class Product 3 { 4 public string Name { get; set; } 5 public decimal Price { get; set; } 6 public string ImageUrl { get; set; } 7 } 8 9 // ViewModel(简化示例,实际用CommunityToolkit.MVVM) 10 public class MainViewModel 11 { 12 // 数据源(ObservableCollection支持自动更新UI) 13 public ObservableCollection<Product> Products { get; } = new() 14 { 15 new Product { Name = "手机", Price = 2999, ImageUrl = "images/phone.png" }, 16 new Product { Name = "电脑", Price = 5999, ImageUrl = "images/computer.png" }, 17 new Product { Name = "平板", Price = 1999, ImageUrl = "images/tablet.png" } 18 }; 19 20 // 选中项(绑定UI) 21 public Product SelectedProduct { get; set; } 22 }
步骤 2:XAML 绑定并自定义模板
1 <Window x:Class="WpfItemsDemo.MainWindow" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:local="clr-namespace:WpfItemsDemo"> 5 <Window.DataContext> 6 <local:MainViewModel/> 7 </Window.DataContext> 8 9 <!-- ListBox绑定数据源+自定义ItemTemplate --> 10 <ListBox 11 Width="300" 12 Height="200" 13 ItemsSource="{Binding Products}" 14 SelectedItem="{Binding SelectedProduct}" 15 HorizontalContentAlignment="Stretch"> <!-- 让子项填满宽度 --> 16 17 <!-- 1. 单个子项的渲染模板 --> 18 <ListBox.ItemTemplate> 19 <DataTemplate> 20 <Grid Margin="5" ColumnDefinitions="80,*,80"> 21 <!-- 图片 --> 22 <Image Grid.Column="0" Source="{Binding ImageUrl}" Width="60" Height="60" Stretch="UniformToFill"/> 23 <!-- 名称 --> 24 <TextBlock Grid.Column="1" Text="{Binding Name}" FontSize="14" VerticalAlignment="Center"/> 25 <!-- 价格 --> 26 <TextBlock Grid.Column="2" Text="{Binding Price, StringFormat='¥{0}'}" Foreground="Red" VerticalAlignment="Center"/> 27 </Grid> 28 </DataTemplate> 29 </ListBox.ItemTemplate> 30 31 <!-- 2. 子项的布局方式(默认是Vertical StackPanel,改成WrapPanel横向排列) --> 32 <ListBox.ItemsPanel> 33 <ItemsPanelTemplate> 34 <WrapPanel Orientation="Horizontal" /> 35 </ItemsPanelTemplate> 36 </ListBox.ItemsPanel> 37 38 <!-- 3. 子项容器(ListBoxItem)的样式(自定义选中态) --> 39 <ListBox.ItemContainerStyle> 40 <Style TargetType="ListBoxItem"> 41 <Setter Property="Margin" Value="5"/> 42 <Setter Property="Padding" Value="0"/> 43 <Setter Property="Background" Value="White"/> 44 <Setter Property="BorderBrush" Value="#E0E0E0"/> 45 <Setter Property="BorderThickness" Value="1"/> 46 <Setter Property="CornerRadius" Value="8"/> 47 <!-- 选中态样式 --> 48 <Style.Triggers> 49 <Trigger Property="IsSelected" Value="True"> 50 <Setter Property="Background" Value="#0078D7"/> 51 <Setter Property="Foreground" Value="White"/> 52 </Trigger> 53 </Style.Triggers> 54 </Style> 55 </ListBox.ItemContainerStyle> 56 </ListBox> 57 </Window>
场景 3:多类型子项(ItemTemplateSelector)
当列表中有不同类型的子项(如文本项 + 图片项),可通过
ItemTemplateSelector动态选择模板:步骤 1:定义模板选择器
1 // 自定义模板选择器 2 public class MyItemTemplateSelector : DataTemplateSelector 3 { 4 // 文本项模板 5 public DataTemplate TextItemTemplate { get; set; } 6 // 图片项模板 7 public DataTemplate ImageItemTemplate { get; set; } 8 9 public override DataTemplate SelectTemplate(object item, DependencyObject container) 10 { 11 // 根据子项类型选择模板 12 if (item is string text) 13 { 14 return TextItemTemplate; 15 } 16 else if (item is Uri imageUri) 17 { 18 return ImageItemTemplate; 19 } 20 return base.SelectTemplate(item, container); 21 } 22 }
步骤 2:XAML 使用模板选择器
1 <Window.Resources> 2 <!-- 文本项模板 --> 3 <DataTemplate x:Key="TextItemTemplate"> 4 <TextBlock Text="{Binding}" FontSize="14" Margin="5"/> 5 </DataTemplate> 6 7 <!-- 图片项模板 --> 8 <DataTemplate x:Key="ImageItemTemplate"> 9 <Image Source="{Binding}" Width="100" Height="80" Margin="5" Stretch="UniformToFill"/> 10 </DataTemplate> 11 12 <!-- 模板选择器实例 --> 13 <local:MyItemTemplateSelector 14 x:Key="MyTemplateSelector" 15 TextItemTemplate="{StaticResource TextItemTemplate}" 16 ImageItemTemplate="{StaticResource ImageItemTemplate}"/> 17 </Window.Resources> 18 19 <!-- ItemsControl使用模板选择器 --> 20 <ItemsControl 21 ItemsSource="{Binding MixedItems}" 22 ItemTemplateSelector="{StaticResource MyTemplateSelector}"> 23 <!-- MixedItems = new List<object> { "标题1", new Uri("images/1.png"), "标题2", new Uri("images/2.png") } --> 24 </ItemsControl>
场景 4:自定义 ItemsControl(无选中逻辑的纯展示列表)
如果只需要展示列表,不需要选中、下拉等逻辑,可直接使用
ItemsControl(最轻量的多内容控件),或继承它自定义控件:1 // 自定义卡片列表控件 2 public class CardListView : ItemsControl 3 { 4 static CardListView() 5 { 6 DefaultStyleKeyProperty.OverrideMetadata(typeof(CardListView), 7 new FrameworkPropertyMetadata(typeof(CardListView))); 8 } 9 }
1 <!-- Themes/Generic.xaml 中的自定义样式(核心是ItemsPresenter) --> 2 <Style TargetType="local:CardListView"> 3 <Setter Property="Template"> 4 <Setter.Value> 5 <ControlTemplate TargetType="local:CardListView"> 6 <Border Background="White" Padding="10"> 7 <!-- 必须保留ItemsPresenter,否则子项无法显示 --> 8 <ItemsPresenter/> 9 </Border> 10 </ControlTemplate> 11 </Setter.Value> 12 </Setter> 13 <!-- 默认布局:网格布局(每行3列) --> 14 <Setter Property="ItemsPanel"> 15 <Setter.Value> 16 <ItemsPanelTemplate> 17 <UniformGrid Columns="3" Margin="5"/> 18 </ItemsPanelTemplate> 19 </Setter.Value> 20 </Setter> 21 </Style>
1 <!-- 使用自定义卡片列表 --> 2 <local:CardListView ItemsSource="{Binding Products}"> 3 <local:CardListView.ItemTemplate> 4 <DataTemplate> 5 <Border Background="#F5F5F5" CornerRadius="8" Padding="10" Margin="5" Width="200" Height="150"> 6 <StackPanel> 7 <TextBlock Text="{Binding Name}" FontSize="16" FontWeight="Bold"/> 8 <TextBlock Text="{Binding Price, StringFormat='¥{0}'}" Margin="0 5 0 0" Foreground="Red"/> 9 </StackPanel> 10 </Border> 11 </DataTemplate> 12 </local:CardListView.ItemTemplate> 13 </local:CardListView>
五、多项内容模型的性能优化
当列表项数量大(如 1000+),需优化性能,核心技巧:
- 启用虚拟化:使用
VirtualizingStackPanel作为 ItemsPanel(默认 ListBox/ListView 已启用),只渲染可视区域的子项:
1 <ListBox.ItemsPanel> 2 <ItemsPanelTemplate> 3 <VirtualizingStackPanel VirtualizationMode="Recycling"/> <!-- 复用容器,性能更好 --> 4 </ItemsPanelTemplate> 5 </ListBox.ItemsPanel>
- 减少视觉树复杂度:ItemTemplate 尽量扁平化(少嵌套面板),避免每个子项有复杂 UI;
- 使用轻量级容器:优先用 ItemsControl(无选中逻辑),而非 ListBox(有额外选中逻辑);
- 延迟加载数据源:大数据集分批次加载,避免一次性绑定所有数据。
六、多项内容模型 vs 单一内容模型(核心区别)
1 维度 多项内容模型(ItemsControl) 单一内容模型(ContentControl) 2 核心属性 Items/ItemsSource Content 3 子项数量 0~N 个 0~1 个 4 核心占位符 ItemsPresenter ContentPresenter 5 子项渲染 ItemTemplate(统一模板) ContentTemplate(单一模板) 6 布局方式 ItemsPanel 自定义布局 无专门布局属性(Content 是单个元素) 7 典型控件 ListBox、ComboBox、Menu Button、Label、Window
总结
- WPF 多项内容模型(Items Model)基于
ItemsControl基类,核心是通过ItemsSource(动态数据源)/Items(静态子项)管理多子项,ItemTemplate定义子项外观,ItemsPanel控制布局; - 核心渲染逻辑:数据源 → 生成 Item 容器 → 应用模板 → ItemsPanel 布局 → ItemsPresenter 显示;
- 实战中优先使用
ItemsSource绑定ObservableCollection<T>(MVVM),大数据列表需启用虚拟化优化性能; - 自定义多内容控件需继承
ItemsControl,并在模板中保留ItemsPresenter(否则子项无法显示)。
浙公网安备 33010602011771号