多重内容模式Items

WPF 中的多项内容模型(Items Model):列表类控件的核心逻辑

WPF 的多项内容模型(Items Model) 是专门为 “需要展示多个子项” 的控件设计的核心规则(对应你提到的Items),是 ListBox、ComboBox、ListView、Menu 等列表类控件的底层基础。简单来说:
多项内容模型允许控件容纳一个元素集合(而非单个元素),并通过统一的规则渲染、布局、交互这些子项,是 WPF 实现灵活列表展示的核心。

一、多项内容模型的核心定位

1. 为什么需要多项内容模型?

单一内容模型(ContentControl)只能放一个元素(如 Button 的 Content),但实际开发中需要展示 “列表、菜单、下拉选项” 等多元素场景 —— 多项内容模型就是为解决这个问题而生,它提供了:
  • 批量管理子项的能力(添加 / 删除 / 绑定数据源);
  • 统一渲染每个子项的模板;
  • 自定义子项布局的方式;
  • 子项的选中、交互等通用逻辑。

2. 核心基类: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

  • 互斥性:不要同时使用ItemsItemsSource——ItemsSource 优先级更高,设置后 Items 会被清空;
  • MVVM 原则:优先使用ItemsSource绑定数据源(如ObservableCollection<T>),而非手动操作Items(符合 MVVM,解耦 UI 和数据)。

三、多项内容模型的核心渲染逻辑

多项内容模型的渲染流程是理解其底层的关键,核心分为 3 步:

image

核心组件: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+),需优化性能,核心技巧:
  1. 启用虚拟化:使用VirtualizingStackPanel作为 ItemsPanel(默认 ListBox/ListView 已启用),只渲染可视区域的子项:
1 <ListBox.ItemsPanel>
2     <ItemsPanelTemplate>
3         <VirtualizingStackPanel VirtualizationMode="Recycling"/> <!-- 复用容器,性能更好 -->
4     </ItemsPanelTemplate>
5 </ListBox.ItemsPanel>
  1. 减少视觉树复杂度:ItemTemplate 尽量扁平化(少嵌套面板),避免每个子项有复杂 UI;
  2. 使用轻量级容器:优先用 ItemsControl(无选中逻辑),而非 ListBox(有额外选中逻辑);
  3. 延迟加载数据源:大数据集分批次加载,避免一次性绑定所有数据。

六、多项内容模型 vs 单一内容模型(核心区别)

1 维度    多项内容模型(ItemsControl)    单一内容模型(ContentControl)
2 核心属性    Items/ItemsSource    Content
3 子项数量    0~N 个    0~14 核心占位符    ItemsPresenter    ContentPresenter
5 子项渲染    ItemTemplate(统一模板)    ContentTemplate(单一模板)
6 布局方式    ItemsPanel 自定义布局    无专门布局属性(Content 是单个元素)
7 典型控件    ListBox、ComboBox、Menu    Button、Label、Window

总结

  1. WPF 多项内容模型(Items Model)基于ItemsControl基类,核心是通过ItemsSource(动态数据源)/Items(静态子项)管理多子项,ItemTemplate定义子项外观,ItemsPanel控制布局;
  2. 核心渲染逻辑:数据源 → 生成 Item 容器 → 应用模板 → ItemsPanel 布局 → ItemsPresenter 显示;
  3. 实战中优先使用ItemsSource绑定ObservableCollection<T>(MVVM),大数据列表需启用虚拟化优化性能;
  4. 自定义多内容控件需继承ItemsControl,并在模板中保留ItemsPresenter(否则子项无法显示)。
 
posted on 2026-03-22 15:59  工业搬砖猿Lee  阅读(5)  评论(0)    收藏  举报