Home > OS >  How to Cleanup the ListViewItem's state before they get reused?
How to Cleanup the ListViewItem's state before they get reused?

Time:10-08

The problem is that the listview uses virtualization, and when the items change their appearance, they remain after list is refreshed. This is an answer from Microsoft engineers

This is caused by the virtualizing nature of ListView's default panel (ItemsStackPanel). It essentially reuses the same ListViewItem (containers) and when it reuses them it might be in a state that is not correct. When you use stack panel, there is no virtualization and every item gets a container (no reuse) which as you can imagine can get very time consuming if there are many items in your list. You can try using PrepareContainerForItemOverride, ClearContainerForItemOverride to cleanup the ListViewItem's state before they get reused. Even better would be to use the ContainerContentChanging event and do it there so you don't need a derived type.

Unfortunately I could not find an example for using the PrepareContainerForItemOverride, ClearContainerForItemOverride and ContainerContentChanging methods.

How can I Clear the ListViewItem's state in the ContainerContentChanging?

UPDATE:

UserControl:

<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>

code behind:

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;
        }
    }

and this is my listview

<ListView
            x:Name="listv"
            ItemsSource="{x:Bind Subtitles, Mode=OneWay}">
            <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 });

            }
        }

CodePudding user response:

ListViewItem's state before they get reused

For this scenario, the better way is using mvvm binding and implement INotifyPropertyChanged for model class. For more detail please refer to Data binding in depth.

For example

Model Class

public class Model : INotifyPropertyChanged
{
    public Model(string name)
    {
        this.Name = name;
    }
   private string _name;

    public string Name
    {
        get
        {
            return _name;
        }
        set
        {
            _name = value;
            NotifyPropertyChanged();
        }
    }

    private bool _visible;
    public bool Visible
    {

        get => _visible;
        set
        {
            _visible = value;
            NotifyPropertyChanged();
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
    {
        if (PropertyChanged != null)
        {
            // PropertyChanged is always null.
            PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Xaml

<ListView x:Name="MyList">
    <ListView.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding Name}" />
                <SymbolIcon
            x:Name="MySbl"
            HorizontalAlignment="Right"
            Symbol="Accept"
            Visibility="{Binding Visible}" />
            </StackPanel>

        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

Usage

MyList.ItemsSource = new ObservableCollection<Model> {
        new Model("hello"),
          new Model("hello1"),
            new Model("hello2"),
              new Model("hello3"),
                new Model("hello4"),
                  new Model("hello5"),
                    new Model("hello6"),
        };
  • Related