2016-07-11 32 views
13

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

当数据填充(属性设置和通知提高)时,我在profiler中看到2个布局尖峰,第二个在通话listView.ScrollIntoView()后发生。

我的理解是:

  1. ListView加载数据通过结合和画面上的项目,从指数0
  2. 开始然后我叫listView.ScrollIntoView()创建ListViewItem
  3. 现在ListView第二次做(创建ListViewItem s)。

如何防止发生两次去虚拟化(我不希望在发生ScrollIntoView之前发生)?


我试图使用ListBox作出repro。

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()); 
    } 
} 

调试 - 性能分析器 - 申请时间表...等一会,单击按钮,稍等一下,关闭窗口。您将看到VirtualizingStackPanel的2个布局通行证。我的目标是只有一个,我不知道如何。

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

+0

一旦项目集合已添加到UI,您是否总是希望将最后一项放入视图中?它不能解决两个布局传递的问题,但如果是这种情况,您可能会在将项目添加到UI之前更改项目的排序方式,以便使要显示的项目成为集合中的第一个项目 – Bijington

+0

@Bijington,集合已经排序(按日期,未在mcve中显示),我不应该改变顺序。它可以是任何项目(在实际项目中它是多选'ListView',滚动到最后选择的项目)。理想情况下,我正在寻找在MVVM应用程序中存储/恢复ListView状态的方法(选择被处理,但滚动位置不是,它用* ScrollIntoView处理),双重去虚拟化是XY-问题(但我对'ScrollIntoView'确定,只有两次解除虚拟化是问题)。 – Sinatr

+0

我尽量多的,但我认为这是值得一问。这听起来像是一个有待解决的有趣问题。你需要告诉'ListView'它以某种方式加载它的内容之前它的滚动位置。你能否以某种方式子类化ListView并阻止渲染,直到完成加载传递(也许是IsLoadingContent标志),以便您可以分配项目并标记需要选择哪些项目并将其展示到视图中? – Bijington

回答

0

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

  1. VirtualizingStackPanel。使用正常的面板模板的StackPanel。
  2. 使DataTemplate的外层成为此处的LazyControl:http://blog.angeloflogic.com/2014/08/lazycontrol-in-junglecontrols.html
  3. 确保您在该LazyControl上设置高度。

我通常会从这种方法中获得良好的性能。为了使它完全符合你的要求,你可能需要向LazyControl添加一些额外的逻辑来等待一些标志被设置(在你调用滚动方法之后)。