wpf ScrollIntoView和ListView与虚拟化

drnojrws  于 2023-06-24  发布在  其他
关注(0)|答案(2)|浏览(214)

我有ListView(默认情况下虚拟化是打开的),ItemsSource绑定到ObservableCollection<Item>属性。
当数据被填充时(属性被设置并且通知被提升),我在分析器中看到2个布局峰值,第二个峰值发生在调用listView.ScrollIntoView()之后。
我的理解是:

  1. ListView通过绑定加载数据,并从索引0开始为屏幕上的项目创建ListViewItem
    1.然后调用listView.ScrollIntoView()
    1.现在ListView第二次执行该操作(创建ListViewItem s)。
    如何防止去虚拟化发生两次(我不希望在ScrollIntoView之前发生一次)?
    我试着用ListBox做一个repro。
    示例1:
<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。我的目标是只生一个,但我不知道怎么做。
repro的问题是模拟负载(在创建ListViewItemis expensive时),但我希望它现在能更清楚地演示这个问题。

px9o7tmv

px9o7tmv1#

这是我在chatgpt找到的,希望能帮到你。
您观察到的行为是由于ListBox控件的虚拟化机制造成的。默认情况下,当调用ScrollIntoView时,ListBox需要确保所请求的项在屏幕上可见。这可能导致生成附加的ListBoxItem并触发第二布局通道。
为了防止这种行为并且只有一次布局传递,可以在填充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 attached属性设置为false,可以有效地禁用该特定操作的虚拟化。填充ListBox后,通过将IsVirtualizing设置回true来重新启用虚拟化。这样,您就可以确保在填充过程中只发生一次布局过程,并且随后的ScrollIntoView不会触发额外的布局过程。

siotufzp

siotufzp2#

Scroll方法通常在VirtualizingStackPanel上不能很好地工作。为了解决这个问题,我使用以下解决方案。
1.删除VirtualizingStackPanel。使用普通的StackPanel作为面板模板。
1.从这里将DataTemplate的外层设置为LazyControl:http://blog.angeloflogic.com/2014/08/lazycontrol-in-junglecontrols.html
1.确保在LazyControl上设置了高度。
我通常从这种方法中获得良好的性能。要使它完全按照您的要求执行,您可能需要向LazyControl添加一些额外的逻辑,以等待设置某个标志(在调用scroll方法之后)。

相关问题