2016-06-29 4 views
6

Я пытаюсь улучшить производительность с помощью своего приложения WPF, и у меня возникают проблемы со сложным элементом ItemsControl. Хотя я добавил виртуализацию, все еще есть проблема с производительностью, и я думаю, что я решил, почему.Есть ли способ использовать виртуализацию со скрытыми панелями или расширителями?

Каждый элемент содержит ряд расширяемых областей. Таким образом, пользователь видит сводку в начале, но может развернуться, расширяя, чтобы увидеть больше информации. Вот как это выглядит:

enter image description here

Как вы можете видеть, что есть некоторые вложенные ItemsControls. Таким образом, каждый элемент верхнего уровня имеет кучу скрытых элементов управления. Виртуализация предотвращает загрузку элементов вне экрана, но не скрытые элементы самих элементов. В результате относительно простая первоначальная компоновка занимает значительное время. Выбрасывая некоторые из этих просмотров, 87% времени тратится на разбор и макет, и загрузка занимает несколько секунд.

Я бы предпочел, чтобы потребовалось 200 мс для расширения, когда (если!) Пользователь решает, а не 2s, чтобы загрузить страницу в целом.

Просить совета действительно. Однако я не могу придумать хороший способ добавления элементов управления с помощью MVVM. Есть ли какой-либо расширитель или виртуализация на основе видимости, поддерживаемая в WPF, или я буду создавать свою собственную реализацию?

87% показатель приходит от диагностики:

enter image description here

+1

Что такое * «скрытые элементы внутри самих элементов» *? Виртуализация не создаст [ContentPresenter'] (http://stackoverflow.com/a/12437064/1997232), но где-то у вас есть 'ObservableCollection <>', который должен быть полностью загружен. Итак, что это за '2s' включает? Вы сделали профилирование, чтобы увидеть, что на самом деле является узким местом? – Sinatr

+0

Вы хотите виртуализировать часть элемента, которая находится внутри 'Expander' (для созданных элементов)? Если да, то вы можете обрабатывать свернутое состояние в ViewModel, чтобы ** добавить больше данных ** при его расширении (например, используя 'object' для хранения этого шаблона данных и данных, чтобы создать визуальную визуальность, если это не' null'). – Sinatr

+0

Вот как это делается. Вы можете отложить получение данных до тех пор, пока они не будут открыты. Я удивлен, что занимает 2 секунды. – Paparazzi

ответ

5

Если вы просто

- Expander 
     Container 
      some bindings 
    - Expander 
      Container 
       some bindings 
+ Expander 
+ Expander 
... invisible items 

Тогда да, Container и все переплеты инициализируются в тот момент, когда отображается вид (и ItemsControl создает ContentPresenter для видимых элементов).

Если вы хотите виртуализировать содержание Expander когда она разрушилась, то вы можете использовать Data-Templating

public ObservableCollection<Item> Items = ... // bind ItemsControl.ItemsSource to this 

class Item : INotifyPropertyChanged 
{ 
    bool _isExpanded; 
    public bool IsExpanded // bind Expander.IsExpanded to this 
    { 
     get { return _isExpanded; } 
     set 
     { 
      Data = value ? new SubItem(this) : null; 
      OnPropertyChanged(nameof(Data)); 
     } 
    } 

    public object Data {get; private set;} // bind item Content to this 
} 

public SubItem: INotifyPropertyChanged { ... } 

Я надеюсь, нет необходимости объяснять, как сделать данные-шаблоны из SubItem в XAML.

Если вы сделаете это, то сначала Data == null и ничего, кроме Expander. Как только он будет расширен (по пользователю или программно), просмотр создаст визуальные эффекты.

+0

Использование шаблонов - отличная идея, я реализовал ее в значительной степени, как вы описываете, и производительность значительно улучшена. Благодаря! – Joe

1

Мне показалось, что я поставил детали решения, что в значительной степени является прямой реализацией ответа Синатра.

Я использовал элемент управления контентом с помощью очень простого набора шаблонов данных. Селектор шаблона просто проверяет, является ли элемент контента имеет нулевое значение, и выбирает между двумя шаблонами данных:

public class VirtualizationNullTemplateSelector : DataTemplateSelector 
{ 
    public DataTemplate NullTemplate { get; set; } 
    public DataTemplate Template { get; set; } 

    public override DataTemplate SelectTemplate(object item, DependencyObject container) 
    { 
     if (item == null) 
     { 
      return NullTemplate; 
     } 
     else 
     { 
      return Template; 

     } 
    } 
} 

Причина этого заключается в том, что ContentControl я использовал до сих пор выкладывает шаблон данных, даже если содержание является нулевым.Поэтому я эти два шаблона в XAML:

  <ContentControl Content="{Binding VirtualizedViewModel}" Grid.Row="1" Grid.ColumnSpan="2" ><!--Visibility="{Binding Expanded}"--> 
       <ContentControl.Resources> 
        <DataTemplate x:Key="Template"> 
         <StackPanel> 
          ...complex layout that isn't often seen... 
         </StackPanel> 
        </DataTemplate> 
        <DataTemplate x:Key="NullTemplate"/> 
       </ContentControl.Resources> 
       <ContentControl.ContentTemplateSelector> 
        <Helpers:VirtualizationNullTemplateSelector Template="{StaticResource Template}" NullTemplate="{StaticResource NullTemplate}"/> 
       </ContentControl.ContentTemplateSelector> 
      </ContentControl> 

Наконец, вместо того, чтобы использовать новый класс для подпункта, это довольно просто создать «VirtualizedViewModel» объект в модели представления, ссылающийся «это ":

private bool expanded; 
    public bool Expanded 
    { 
     get { return expanded; } 
     set 
     { 
      if (expanded != value) 
      { 
       expanded = value; 
       NotifyOfPropertyChange(() => VirtualizedViewModel); 
       NotifyOfPropertyChange(() => Expanded); 
      } 
     } 
    } 


    public MyViewModel VirtualizedViewModel 
    { 
     get 
     { 
      if (Expanded) 
      { 
       return this; 
      } 
      else 
      { 
       return null; 
      } 
     } 
    } 

Я сократил время загрузки 2-3s примерно на 75%, и теперь это кажется намного более разумным.