我正在尝试使用我的WPF应用程序来提高性能,并且我遇到了复杂的ItemsControl问题。虽然我已经添加了虚拟化,但仍然存在性能问题,我想我已经解决了原因。
每个项目都包含一系列可扩展区域。因此,用户在开始时会看到摘要,但可以通过展开来查看更多信息。这是它的外观:
如您所见,有一些嵌套的ItemsControls。所以每个顶级项目都有一堆隐藏控件。虚拟化可防止加载屏幕外项目,但不会阻止项目本身内的隐藏项目。结果,相对简单的初始布局花费了大量时间。点击其中一些视图,87%的时间用于解析和布局,加载需要几秒钟。
我更倾向于在用户决定使用(如果!)时展开200ms,而不是2s来整个页面加载。
真的要求建议。我想不出使用MVVM添加控件的好方法。在WPF中是否支持任何扩展器或基于可见性的虚拟化,或者我将创建自己的实现?
87%的数字来自诊断:
答案 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创建了。将其附加到应用程序后,您可以使用左上方的文本框过滤视觉树。如果您在树中找不到一个控件,则表明它尚未创建。