【问题标题】:ScrollIntoView and ListView with virtualizationScrollIntoView 和 ListView 与虚拟化
【发布时间】:2016-07-11 09:14:11
【问题描述】:

我有ListView(虚拟化默认开启),ItemsSource 绑定到ObservableCollection<Item> 属性。

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

我的理解是:

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

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

Debug - Performance Profiler - Application Timeline... 稍等,单击按钮,稍等,关闭窗口。您将看到 2 个带有 VirtualizingStackPanel 的布局通道。我的目标是只有一个,但我不知道怎么做。

repro的问题是模拟负载(创建ListViewItemis expensive时),但我希望现在能更清楚地证明问题。

【问题讨论】:

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

标签: c# wpf listview mvvm virtualization


【解决方案1】:

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

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

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

【讨论】:

    猜你喜欢
    • 2011-05-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多