有没有办法使用隐藏面板或扩展器的虚拟化?

时间:2016-06-29 11:04:19

标签: c# wpf performance virtualization

我正在尝试使用我的WPF应用程序来提高性能,并且我遇到了复杂的ItemsControl问题。虽然我已经添加了虚拟化,但仍然存在性能问题,我想我已经解决了原因。

每个项目都包含一系列可扩展区域。因此,用户在开始时会看到摘要,但可以通过展开来查看更多信息。这是它的外观:

enter image description here

如您所见,有一些嵌套的ItemsControls。所以每个顶级项目都有一堆隐藏控件。虚拟化可防止加载屏幕外项目,但不会阻止项目本身内的隐藏项目。结果,相对简单的初始布局花费了大量时间。点击其中一些视图,87%的时间用于解析和布局,加载需要几秒钟。

我更倾向于在用户决定使用(如果!)时展开200ms,而不是2s来整个页面加载。

真的要求建议。我想不出使用MVVM添加控件的好方法。在WPF中是否支持任何扩展器或基于可见性的虚拟化,或者我将创建自己的实现?

87%的数字来自诊断:

enter image description here

4 个答案:

答案 0 :(得分:5)

如果您只是

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

然后是,Container并且在显示视图时初始化所有绑定(ItemsControl为可见项创建ContentPresenter

如果要在Expander折叠时虚拟化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除了$.ajax({ type: 'get', url: 'http://XX.XX.XX.XX/account/status?callback=?', dataType : 'jsonp', contentType: "application/json", jsonp : "callback", jsonpCallback: "jsonpcallback", success: function(data){ console.log(data); }, error: function(){ alert('500 error!') } }); 之外什么都没有加载。一旦扩展(通过用户或编程),视图将创建视觉效果。

答案 1 :(得分:1)

我认为我已经提供了解决方案的细节,这几乎是对Sinatr的答案的直接实现。

我使用内容控件,使用非常简单的数据模板选择器。模板选择器只检查内容项是否为空,并在两个数据模板之间进行选择:

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;

        }
    }
}

原因是即使内容为null,我使用的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>

最后,不是为子项使用全新的类,而是创建一个&#34; VirtualizedViewModel&#34;视图模型中引用&#34;此&#34;:

的对象
    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%,现在似乎更合理了。

答案 2 :(得分:1)

这个简单的解决方案对我有帮助:

<Expander x:Name="exp1">
   <Expander.Header>
     ...
   </Expander.Header>
   <StackPanel
      Margin="10,0,0,0"
      Visibility="{Binding ElementName=exp1, Path=IsExpanded, Converter={StaticResource BooleanToVisibilityConverter}}">
      <Expander x:Name="exp2">
        <Expander.Header>
          ...
        </Expander.Header>
        <StackPanel
          Margin="10,0,0,0"
          Visibility="{Binding ElementName=exp2, Path=IsExpanded, Converter={StaticResource BooleanToVisibilityConverter}}">

答案 3 :(得分:0)

更简单的方法是将内容的默认“可见性”更改为“已折叠”。在这种情况下,WPF最初不会创建它,而是仅在触发器将其设置为可见时创建:

<Trigger Property="IsExpanded" Value="true">
  <Setter Property="Visibility"Value="Visible" TargetName="ExpandSite"/>
</Trigger>

此处“ ExpandSite”是Expander控件的默认ControlTemplate中的ContentPresenter。 请注意,此问题已在.NET中修复-请参见github上WPF源中的默认样式。

如果您使用的是旧版本,则仍然可以使用此固定控件模板以隐式样式更新旧版本。

您可以将相同的技术应用于任何其他面板或控件。

很容易检查控件是否已经用Snoop创建了。将其附加到应用程序后,您可以使用左上方的文本框过滤视觉树。如果您在树中找不到一个控件,则表明它尚未创建。