使用ItemsControl进行UI缓存

时间:2014-11-15 23:33:22

标签: .net wpf performance itemscontrol

我的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之外,还有不同的详细视图,可以从左侧列表中选择不同的项目类型。所以我基本上需要我拥有的结构,它只需要更快。

Screenshot

整个项目是开源的,如果您愿意,可以查看完整的解决方案:https://github.com/dg9ngf/FieldLog

1 个答案:

答案 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>