如何在ListViewItem复用前清理其状态?ContainerContentChanging使用方法咨询
问题背景
ListView开启虚拟化特性后,列表项外观发生变更后刷新列表,变更后的状态会出现残留。
微软官方给出的原因说明:
该问题由ListView默认面板(ItemsStackPanel)的虚拟化特性导致,其会复用相同的ListViewItem(容器),复用的时候容器可能处于错误的旧状态。若使用普通StackPanel则不会开启虚拟化,每个列表项都会生成独立容器(无复用),但当列表项数量较多时该方案性能消耗会非常高。你可以尝试使用PrepareContainerForItemOverride、ClearContainerForItemOverride在ListViewItem复用前清理其状态,更推荐使用ContainerContentChanging事件实现该需求,无需自定义派生类。
需求
现需要通过ContainerContentChanging事件实现ListViewItem状态清理,暂无相关方法的使用示例参考。
现有代码参考
自定义UserControl XAML代码
<Grid> <SwipeControl x:Name="ListViewSwipeContainer"> <Grid VerticalAlignment="Center"> <Grid.RowDefinitions> <RowDefinition Height="auto"/> <RowDefinition Height="auto"/> </Grid.RowDefinitions> <TextBlock Margin="10,5,200,5" HorizontalAlignment="Left" VerticalAlignment="Center" FontSize="18" Text="{Binding ElementName=subsceneView, Path=Title}" TextWrapping="Wrap"/> <AppBarButton Name="OpenFolderButton" Grid.RowSpan="2" MinWidth="75" Margin="10,0,10,0" HorizontalAlignment="Right" VerticalAlignment="Center" Click="OpenFolderButton_Click" Icon="OpenLocal" IsTabStop="False" Label="Open Folder" Visibility="Collapsed"/> <AppBarButton Name="DownloadHoverButton" Grid.RowSpan="2" Margin="10,0,10,0" HorizontalAlignment="Right" VerticalAlignment="Center" Click="DownloadHoverButton_Click" Icon="Download" IsTabStop="False" Label="Download" Visibility="Collapsed"/> </Grid> </SwipeControl> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="HoveringStates"> <VisualState x:Name="HoverButtonsHidden"/> <VisualState x:Name="HoverButtonsShown"> <VisualState.Setters> <Setter Target="DownloadHoverButton.Visibility" Value="Visible"/> </VisualState.Setters> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> </Grid>
自定义UserControl 后台代码
public sealed partial class SubsceneUserControl : UserControl { #region DependencyProperty public static readonly DependencyProperty TitleProperty = DependencyProperty.Register("Title", typeof(string), typeof(SubsceneUserControl), new PropertyMetadata(string.Empty)); public string Title { get { return (string)GetValue(TitleProperty); } set { SetValue(TitleProperty, value); } } #endregion public SubsceneUserControl() { this.InitializeComponent(); } private void UserControl_PointerEntered(object sender, PointerRoutedEventArgs e) { if (e.Pointer.PointerDeviceType == Windows.Devices.Input.PointerDeviceType.Mouse || e.Pointer.PointerDeviceType == Windows.Devices.Input.PointerDeviceType.Pen) { VisualStateManager.GoToState(sender as Control, "HoverButtonsShown", true); } } private void UserControl_PointerExited(object sender, PointerRoutedEventArgs e) { VisualStateManager.GoToState(sender as Control, "HoverButtonsHidden", true); } private void OpenFolderButton_Click(object sender, RoutedEventArgs e) { } private void DownloadHoverButton_Click(object sender, RoutedEventArgs e) { OpenFolderButton.Visibility = Visibility.Visible; DownloadHoverButton.Visibility = Visibility.Collapsed; } // 新增重置状态的公共方法,方便外部调用 public void ResetState() { VisualStateManager.GoToState(this, "HoverButtonsHidden", false); OpenFolderButton.Visibility = Visibility.Collapsed; DownloadHoverButton.Visibility = Visibility.Collapsed; } }
ListView 相关代码
<ListView x:Name="listv" ItemsSource="{x:Bind Subtitles, Mode=OneWay}" ContainerContentChanging="listv_ContainerContentChanging"> <ListView.ItemTemplate> <DataTemplate x:DataType="local:SubsceneDownloadModel"> <local:SubsceneUserControl Title="{x:Bind Title}"/> </DataTemplate> </ListView.ItemTemplate> </ListView>
private void AddItems() { Subtitles?.Clear(); for (int i = 0; i < 10; i++) { Subtitles.Add(new SubsceneDownloadModel { Title = "Test " + i }); } } // 新增ContainerContentChanging事件处理 private void listv_ContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args) { if (args.ItemContainer.ContentTemplateRoot is SubsceneUserControl userControl) { // 复用容器前先重置所有自定义状态 userControl.ResetState(); } }
实现逻辑说明
- 首先在自定义UserControl中新增公共的
ResetState方法,将控件的交互状态、按钮可见性全部重置为初始值 - 给ListView添加
ContainerContentChanging事件,该事件会在容器复用、绑定新数据前触发 - 在事件处理逻辑中获取容器内的UserControl实例,调用重置方法即可清空旧状态,避免残留
- 该方案无需关闭虚拟化,也不需要自定义ListView派生类,性能损耗极低
内容的提问来源于stack exchange,提问作者user16474396
相关产品推荐
相关产品推荐

