具有虚拟化的ScrollIntoView和ListView

问题描述 投票:0回答:2

我有

ListView
(虚拟化默认处于启用状态),其中
ItemsSource
绑定到
ObservableCollection<Item>
属性。

填充数据时(设置属性并发出通知),我在探查器中看到 2 个布局峰值,第二个出现在调用

listView.ScrollIntoView()
之后。

我的理解是:

  1. ListView
    通过绑定加载数据并为屏幕上的项目创建
    ListViewItem
    ,从索引 0 开始。
  2. 然后我打电话给
    listView.ScrollIntoView()
  3. 现在
    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
很昂贵),但我希望它现在能更清楚地演示问题。

c# wpf listview mvvm virtualization
2个回答
0
投票

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

创建 ListViewItem 的成本很高

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

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

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


-1
投票

滚动方法通常在

VirtualizingStackPanel
上效果不佳。为了解决这个问题,我使用以下解决方案。

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

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

© www.soinside.com 2019 - 2024. All rights reserved.