具有虚拟化功能的 ScrollIntoView 和 ListView

ScrollIntoView and ListView with virtualization

提问人:Sinatr 提问时间:7/11/2016 最后编辑:CommunitySinatr 更新时间:6/21/2023 访问量:2330

问:

我有(默认情况下虚拟化处于打开状态),它与属性绑定。ListViewItemsSourceObservableCollection<Item>

当数据被填充(属性被设置并通知被上升)时,我在分析器中看到 2 个布局峰值,第二个发生在调用之后。listView.ScrollIntoView()

我的理解是:

  1. ListView通过绑定加载数据,并从索引 0 开始为屏幕上的项目创建数据。ListViewItem
  2. 然后我打电话给.listView.ScrollIntoView()
  3. 现在第二次(创建 s)。ListViewListViewItem

如何防止这种去虚拟化发生两次(我不希望之前发生一次)?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>

ds]

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

C# WPF ListView MVVM 虚拟化

评论

0赞 Bijington 7/20/2016
是否总是希望在将项目集合添加到 UI 后将最后一个项目带入视图?它不能解决两个布局传递的问题,但如果是这种情况,您可以在将项目添加到 UI 之前更改项目的排序方式,以便要进入视图的项目是集合中的第一个项目
0赞 Sinatr 7/20/2016
@Bijington,集合已经排序(按日期排序,未显示在 mcve 中),我不应该更改顺序。它可以是任何项目(在实际项目中它是多选的,滚动到最后选择的项目)。理想情况下,我正在寻找在 MVVM 应用程序中存储/恢复状态的方法(选择被处理,但滚动位置没有被处理,它以某种方式被处理),双重去虚拟化是一种 XY 问题(但我没问题,只有去虚拟化两次是问题)。ListViewListViewScrollIntoViewScrollIntoView
0赞 Bijington 7/20/2016
我假设了很多,但我认为这值得一问。这听起来当然是一个有趣的问题。在以某种方式加载其内容之前,您需要告诉它的滚动位置。您能否以某种方式子类化并阻止渲染,直到您完成加载传递(可能是 IsLoadingContent 标志),以便您可以分配项目并标记需要选择和进入视图的项目?ListViewListView
0赞 user3308241 10/1/2016
如果您将 ListBox 的可见性设置为 Hidden,然后在调用 ScrollIntoView 后使其可见,它会解决问题吗?

答:

-1赞 Brannon 11/22/2016 #1

Scroll 方法通常不能很好地工作在 .为了解决这个问题,我使用以下解决方案。VirtualizingStackPanel

  1. 抛弃 .使用普通的 StackPanel 作为面板模板。VirtualizingStackPanel
  2. 从这里开始,使 DataTemplate 的外层成为 LazyControl: http://blog.angeloflogic.com/2014/08/lazycontrol-in-junglecontrols.html
  3. 确保在该 LazyControl 上设置高度。

我通常从这种方法中获得良好的性能。为了使它完全按照您的要求执行操作,您可能需要向 LazyControl 添加一些额外的逻辑,以等待设置某些标志(在调用 scroll 方法之后)。

0赞 Vlad Isoc 12/7/2023 #2

答案就在你最后的陈述中:

当创建 ListViewItem 时成本高昂

您可以回收项目的布局(只要它们具有相同的布局)。此外,像素滚动使滚动比单位滚动更令人窒息。

<ListBox VirtualizingPanel.VirtualizationMode="Recycling"
         VirtualizingPanel.ScrollUnit="Pixel">

我猜你设置Button_Click函数是为了举例;请注意,如果在 ListBox 控件主动使用时将许多项添加到 ObservableCollection 中,则会破坏性能;不幸的是,该类没有 AddRange 函数,因此您需要即兴创作;检查此线程