滚动到具有虚拟化功能的视图和列表视图
本文关键字:视图 列表 功能 虚拟化 滚动 | 更新日期: 2023-09-27 17:56:22
我有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
很昂贵时),但我希望它现在更清楚地展示了问题。
这是我在chatgpt中找到的,希望可以提供帮助。
您观察到的行为是由于 ListBox 控件的虚拟化机制。默认情况下,当您调用 ScrollIntoView 时,列表框需要确保请求的项在屏幕上可见。这可能会导致生成其他列表框项并触发第二个布局传递。
若要防止此行为并且只有一个布局阶段,可以在填充 ListBox 之前暂时禁用虚拟化,然后在之后重新启用它。下面是包含此方法的代码的更新版本:
public partial class MainWindow : Window
{
ViewModel _vm = new ViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = _vm;
}
void Button_Click(object sender, RoutedEventArgs e)
{
// Disable virtualization
listBox.SetValue(VirtualizingStackPanel.IsVirtualizingProperty, false);
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);
// Enable virtualization after populating the ListBox
listBox.SetValue(VirtualizingStackPanel.IsVirtualizingProperty, true);
listBox.ScrollIntoView(list.Last());
}
}
通过在填充 ListBox 之前将VirtualizingStackPanel.IsVirtualizing
附加属性设置为 false
,可以有效地禁用该特定操作的虚拟化。填充列表框后,通过将IsVirtualizing
设置回 true
来重新启用虚拟化。这样,您可以确保在填充期间仅发生一次布局传递,并且后续的 ScrollIntoView 不会触发其他布局传递。
滚动方法通常不能很好地处理VirtualizingStackPanel
。为了解决这个问题,我使用以下解决方案。
- 抛弃
VirtualizingStackPanel
.使用普通的StackPanel作为面板模板。 - 从这里开始,将 DataTemplate 的外层设为 LazyControl:http://blog.angeloflogic.com/2014/08/lazycontrol-in-junglecontrols.html
- 确保在该 LazyControl 上设置高度。
我通常从这种方法中获得良好的表现。要使其完全按照您的要求执行,您可能需要向 LazyControl 添加一些额外的逻辑以等待设置某个标志(在调用滚动方法之后)。