如何在重新使用 ListViewItem 之前清理它们的状态?
How to Cleanup the ListViewItem's state before they get reused?
问题是列表视图使用虚拟化,当项目改变它们的外观时,它们在列表刷新后仍然存在。这是微软工程师的回答
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.
很遗憾,我找不到使用 PrepareContainerForItemOverride、ClearContainerForItemOverride 和 ContainerContentChanging 的示例方法。
如何在 ContainerContentChanging 中清除 ListViewItem 的状态?
更新:
用户控件:
<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>
隐藏代码:
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;
}
}
这是我的列表视图
<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 });
}
}
ListViewItem's state before they get reused
对于这种情况,更好的方法是使用 mvvm 绑定并为模型 class 实施 INotifyPropertyChanged
。更多详情请参考Data binding in depth.
例如
型号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>
用法
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"),
};
问题是列表视图使用虚拟化,当项目改变它们的外观时,它们在列表刷新后仍然存在。这是微软工程师的回答
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.
很遗憾,我找不到使用 PrepareContainerForItemOverride、ClearContainerForItemOverride 和 ContainerContentChanging 的示例方法。
如何在 ContainerContentChanging 中清除 ListViewItem 的状态?
更新:
用户控件:
<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>
隐藏代码:
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;
}
}
这是我的列表视图
<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 });
}
}
ListViewItem's state before they get reused
对于这种情况,更好的方法是使用 mvvm 绑定并为模型 class 实施 INotifyPropertyChanged
。更多详情请参考Data binding in depth.
例如
型号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>
用法
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"),
};