2016-07-11 7 views
13

У меня есть ListView (виртуализация включена по умолчанию), которая ItemsSource привязана к ObservableCollection<Item>.ScrollIntoView и ListView с виртуализацией

Когда данные заселены (свойство установлено и добавлено уведомление) Я вижу 2 макета всплесков в профилировщике, второй - после звонка listView.ScrollIntoView().

Мое понимание:

  1. ListView загружает данные через связывание и создает ListViewItem для элементов на экране, начиная с индекса 0.
  2. Тогда я называю listView.ScrollIntoView().
  3. И теперь ListView делает это второй раз (создание ListViewItem s).

Как предотвратить повторение виртуализации дважды (я не хочу, чтобы он произошел до 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. Моя цель - иметь только один, и я не знаю, как это сделать.

Проблема с воспроизведением заключается в моделировании нагрузки (при создании ListViewItemis expensive), но я надеюсь, что это более четко продемонстрирует проблему сейчас.

+0

Вы всегда хотите отобразить последний элемент после того, как коллекция элементов была добавлена ​​в пользовательский интерфейс? Он не решает проблему двух пропусков макета, но если это так, вы можете изменить способ сортировки элементов перед добавлением их в пользовательский интерфейс, чтобы элемент, который вы хотите отобразить, - это первый элемент в коллекции – Bijington

+0

@Bijington, коллекция уже отсортирована (по дате, не показана в mcve), я не должен менять порядок. Это может быть любой элемент (в реальном проекте это multi-select 'ListView', прокрутка к последнему выбранному элементу).В идеале я ищу способ хранения/восстановления состояния ListView в приложении MVVM (выбор обрабатывается, но позиция прокрутки отсутствует, это * как-то * обрабатывается с помощью 'ScrollIntoView'), что двойная де виртуализация - это своего рода XY- проблема (но я в порядке с 'ScrollIntoView', проблема - только де-виртуализация). – Sinatr

+0

Я принял столько же, но я подумал, что стоит спросить. Это, безусловно, звучит как интересная проблема для решения. Вы должны указать «ListView» это положение прокрутки, прежде чем загружать контент. Не могли бы вы как-то подклассифицировать «ListView» и предотвратить рендеринг, пока не закончите пропуск нагрузки (возможно, флаг IsLoadingContent), чтобы вы могли назначить элементы, а также отметить, какой элемент нужно выбрать и ввести в поле зрения? – Bijington

ответ

0

Методы прокрутки, как правило, не очень хорошо работают на VirtualizingStackPanel. Чтобы обойти это, я использую следующее решение.

  1. Ditch VirtualizingStackPanel. Пойдите с обычной StackPanel для шаблона панели.
  2. Сделать внешний слой вашего DataTemplate от LazyControl отсюда: http://blog.angeloflogic.com/2014/08/lazycontrol-in-junglecontrols.html
  3. Убедитесь, что вы установили высоту на этом LazyControl.

Как правило, я получаю хорошие результаты из этого подхода. Чтобы он выполнял именно то, что вы просите, вам может потребоваться добавить дополнительную логику в LazyControl, чтобы дождаться установки определенного флага (после вызова метода прокрутки).