一、目的:在WPF开发过程中,经常用到TabControl,也会遇到类似问题,用TabControl绑定数据源ItemsSource时,切换TabItem时,UI上的数据没有持久保存,本文介绍一种处理方式,可以做到缓存页面,只在切换TabItem时Load一次,重复切换TabItem时不会重复Load
二、实现
首先介绍遇到问题的写法
<TabItem Header="TabControl页面数据没有缓存">
<TabControl ItemsSource="{h:GetStudents Count=10}">
<TabControl.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
<DataTemplate DataType="{x:Type h:Student}">
<Grid Loaded="Grid_Loaded">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="5" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBox HorizontalAlignment="Stretch" />
<GridSplitter Grid.Column="1"
Width="5"
ResizeBehavior="PreviousAndNext" />
<h:Form Grid.Column="2"
SelectObject="{Binding}" />
</Grid>
</DataTemplate>
</TabControl.Resources>
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
</TabItem>
运行效果如下:
可以看到,Binding到后台数据的内容可以持久化保存,但是没有Binding(左侧的TextBox)或者UI上的元素(GridSplitter)的位置没有持久化保存
使用ListBox作为容器修改后的写法
<TabItem Header="TabControl持久保存页面">
<TabControl ItemsSource="{h:GetStudents Count=10}">
<TabControl.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
<DataTemplate DataType="{x:Type h:Student}">
<Grid Loaded="Grid_Loaded">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="5" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBox HorizontalAlignment="Stretch" />
<GridSplitter Grid.Column="1"
Width="5"
ResizeBehavior="PreviousAndNext" />
<h:Form Grid.Column="2"
SelectObject="{Binding}" />
</Grid>
</DataTemplate>
</TabControl.Resources>
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ListBox ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=TabControl}, Path=ItemsSource}"
SelectedItem="{Binding RelativeSource={RelativeSource AncestorType=TabControl}, Path=SelectedItem}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Grid />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Visibility" Value="{Binding RelativeSource={RelativeSource Mode=Self}, Path=IsSelected, Converter={StaticResource BooleanToVisibilityConverter}}" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Stretch" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<ContentPresenter />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</TabItem>
相比之前的有问题的写法实际上是增加了TabControl.ContentTemplate的配置,改用ListBox,使用Grid作为ListBox的 ItemsPanel,每次切换TabItem时实际上是切换ListBox中ListBoxItem的显示和隐藏,这样就可以做到切换页面的持久化保存和Load只加载一次的效果。
运行效果如下:
五、需要了解的知识点
TabControl 样式和模板 - WPF .NET Framework | Microsoft Learn
TabControl 类 (System.Windows.Controls) | Microsoft Learn
ListBox 类 (System.Windows.Controls) | Microsoft Learn
System.Windows.Controls 命名空间 | Microsoft Learn
六、源码地址
GitHub - HeBianGu/WPF-ControlDemo: 示例
GitHub - HeBianGu/WPF-ControlBase: Wpf封装的自定义控件资源库
GitHub - HeBianGu/WPF-Control: WPF轻量控件和皮肤库