我的WPF应用程序中存在性能问题。 (我想这不是新事物......)基本上,应用程序包含ListBox
可以选择的项目,以及显示所选项目详细信息的ContentControl
。对于一种类型的项目,涉及ItemsControl
涉及显示子项目列表。这就是问题所在。一旦我删除了ItemsControl
,或者只显示了非常少量的子项(< 3),它就会很快。但是有50个子项目,浏览列表感觉太慢了。
您可能会建议我进行某种虚拟化,但这并不适用于此。在许多情况下,所有项目都适合屏幕,因此无论如何都需要立即显示。无需虚拟化看不见的物品。
我可以将整个应用程序拆分为几个简短的类(视图和视图模型)来演示性能问题。此测试用例可以是downloaded here。只需运行应用程序,从左侧列表中选择一个项目,然后按住向上或向下箭头键移动到其他项目。通常情况下,这会立即显示另一个项目,但这里需要的时间非常明显,每次约160毫秒。
视图的核心如下:
<UserControl ...>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding StackFrameVMs, Mode=OneTime}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="•" Foreground="{Binding ...}"/>
<TextBox Margin="4,0,0,0" Text="{Binding ...}"/>
<TextBox Margin="12,0,0,0" Text="{Binding ...}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</UserControl>
这不是很多UI控件,但我需要它们。实际上在我的实际应用程序中还有一些控件和绑定,但这些对于演示来说已经足够了。当我将DataTemplate
的内容移动到单独的UserControl
并插入时,则需要更长时间。
从之前的概要中我相信ItemsControl
会抛弃并在每次列表更改时重新创建列表项的所有控件,因为选择了不同的项(并且详细信息视图的DataContext
更改)。即使视图本身被重用,因为新选择的项目具有相同的类型并使用相同的DataTemplate
。
有没有办法让ItemsControl
重复使用它创建的项目?也许甚至所有这些,如果其中一个选定的项目需要更少,但下一个需要更多?或者有没有办法以另一种方式改善这个非常简单的用例的性能?
更新 BTW,请注意,此示例代码是我的完整应用程序外观的非常简化的版本。您可能认为可以简化我选择的结构,但请考虑完整的应用程序看起来像下面的屏幕截图。除了ItemsControl
之外,还有不同的详细视图,可以从左侧列表中选择不同的项目类型。所以我基本上需要我拥有的结构,它只需要更快。
整个项目是开源的,如果您愿意,可以查看完整的解决方案:https://github.com/dg9ngf/FieldLog
答案 0 :(得分:0)
我找到了一个解决方案,但代价是启动时间。
将右侧列表放在ItemsControl
中,Grid
作为其面板,这将在启动时加载每个项目,但稍加改动就可以使它看起来像以前一样:< / p>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ListBox Name="LogItemsList" ItemsSource="{Binding LogItems}" SelectedItem="{Binding SelectedItem}"/>
<ItemsControl ItemsSource="{Binding LogItems}" Grid.Column="1">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type vm:FieldLogExceptionItemViewModel}">
<v:FieldLogExceptionItemView Visibility="{Binding IsVisible, Converter={StaticResource BooleanToVis}}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
现在将其添加到MainViewModel
:
/// <summary>
/// Gets or sets a bindable value that indicates SelectedItem
/// </summary>
public FieldLogExceptionItemViewModel SelectedItem
{
get { return (FieldLogExceptionItemViewModel)GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(FieldLogExceptionItemViewModel), typeof(MainViewModel),
new PropertyMetadata(null, (d, e) =>
{
var vm = (MainViewModel)d;
var val = (FieldLogExceptionItemViewModel)e.NewValue;
if(val!=null)
{
foreach (FieldLogExceptionItemViewModel item in vm.LogItems)
{
item.IsVisible = (item==val);
}
}
}));
并将其添加到FieldLogExceptionItemViewModel
:
/// <summary>
/// Gets or sets a bindable value that indicates IsVisible
/// </summary>
public bool IsVisible
{
get { return (bool)GetValue(IsVisibleProperty); }
set { SetValue(IsVisibleProperty, value); }
}
public static readonly DependencyProperty IsVisibleProperty =
DependencyProperty.Register("IsVisible", typeof(bool), typeof(FieldLogExceptionItemViewModel), new PropertyMetadata(false));
然后为了加快速度:
注意BooleanToVis
转换器。您需要实现一个新的BooleanToVisibilityConvereter
,因为系统默认转换器在传递false时使用Visibility.Collapsed
并导致UI在将折叠后的项目更改回Visibility.Visibile
后重新加载,但我们需要什么是Visibility.Hidden
,因为我们需要随时准备好所有内容。
public class BooleanToVis : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (bool)value ? Visibility.Visible : Visibility.Hidden;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
<Window.Resources>
<v:BooleanToVis x:Key="BooleanToVis"/>
</Window.Resources>