我有
ListView
(虚拟化默认处于启用状态),其中 ItemsSource
绑定到 ObservableCollection<Item>
属性。
填充数据时(设置属性并发出通知),我在探查器中看到 2 个布局峰值,第二个出现在调用
listView.ScrollIntoView()
之后。
我的理解是:
ListView
通过绑定加载数据并为屏幕上的项目创建 ListViewItem
,从索引 0 开始。listView.ScrollIntoView()
。ListView
第二次进行(创建ListViewItem
)。如何防止去虚拟化发生两次(我不希望
ScrollIntoView
之前发生一次)?
我尝试使用
ListBox
进行复制。
xaml:
<Grid>
<ListBox x:Name="listBox" ItemsSource="{Binding Items}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
<Button Content="Fill" VerticalAlignment="Top" HorizontalAlignment="Center" Click="Button_Click" />
</Grid>
cs:
public class NotifyPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string property = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
public class ViewModel : NotifyPropertyChanged
{
public class Item : NotifyPropertyChanged
{
bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
OnPropertyChanged();
}
}
}
ObservableCollection<Item> _items = new ObservableCollection<Item>();
public ObservableCollection<Item> Items
{
get { return _items; }
set
{
_items = value;
OnPropertyChanged();
}
}
}
public partial class MainWindow : Window
{
ViewModel _vm = new ViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = _vm;
}
void Button_Click(object sender, RoutedEventArgs e)
{
var list = new List<ViewModel.Item>(1234567);
for (int i = 0; i < 1234567; i++)
list.Add(new ViewModel.Item());
list.Last().IsSelected = true;
_vm.Items = new ObservableCollection<ViewModel.Item>(list);
listBox.ScrollIntoView(list.Last());
}
}
调试 - 性能分析器 - 应用程序时间线...稍等一下,单击按钮,稍等一下,关闭窗口。您将看到 2 个带有
VirtualizingStackPanel
的布局通道。我的目标是只拥有一个,但我不知道如何实现。
重现的问题是模拟负载(创建时
ListViewItem
很昂贵),但我希望它现在能更清楚地演示问题。
答案就在你最后的陈述中:
创建 ListViewItem 的成本很高
您可以回收项目的布局(只要它们具有相同的布局)。此外,像素滚动使滚动比单位滚动更平滑。
<ListBox VirtualizingPanel.VirtualizationMode="Recycling"
VirtualizingPanel.ScrollUnit="Pixel">
我猜你设置 Button_Click 函数是为了举例;请注意,如果在 ListBox 控件主动使用 ObservableCollection 时将许多项添加到 ObservableCollection 中,则会破坏性能;不幸的是,该类没有 AddRange 函数,因此您需要即兴发挥; 检查此线程。
滚动方法通常在
VirtualizingStackPanel
上效果不佳。为了解决这个问题,我使用以下解决方案。
VirtualizingStackPanel
。使用普通的 StackPanel 作为面板模板。我通常会通过这种方法获得良好的性能。为了使其完全按照您的要求执行,您可能需要向 LazyControl 添加一些额外的逻辑以等待设置某些标志(在调用滚动方法之后)。