Оптимизация производительности прокрутки WPF 4.0 TreeView

Прошло слишком много времени с моего последнего вопроса, так что вот новый!

У меня возникают проблемы с производительностью при виртуализации WPF TreeView во время прокрутки. Допустим, у меня есть произвольные сложные элементы управления в моих TreeViewItems, измерение которых может занять много времени, скажем, 10 мс. Прокрутка TreeView вниз может быстро стать очень запаздывающей, так как проход Layout вызывается для каждой единицы прокрутки вниз.

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

Первый заключался в том, чтобы буферизовать последний результат MeasureOverride и использовать его вместо повторного вызова MeasureOverride, если размер не изменился. Это работает, пока у меня есть только один уровень иерархии в дереве. Но как только получаю больше, некоторые элементы не рендерятся (хотя размер правильно зарезервирован). Инвалидация буфера не будет проблемой.

Во-вторых, заменить ControlTemplate элементов управления в treeViewItems во время прокрутки благодаря Trigger, используя очень быстро отображаемый ControlTemplate. К сожалению, я получаю странные исключения, когда перестаю прокручивать элементы, уже прикрепленные к логическому дереву.

Наконец, я не нахожусь в многопоточной среде и поэтому не смогу использовать асинхронные привязки. :(

Вот очень небольшой пример дерева, которое я хотел бы улучшить (это тот, который реализует мою первую идею).

public class Item
{
    public string Index { get; set; }
    public Item Parent { get; set; }

    public IEnumerable<Item> Items
    {
        get
        {
            if (Parent == null)
            {
                for (int i = 0; i < 10; i++)
                {
                    yield return new Item() { Parent = this, Index = this.Index + "." + i.ToString() };
                }
            }
        }
    }
}

public class HeavyControl : ContentControl
{
    protected override Size MeasureOverride(Size constraint)
    {
        Thread.Sleep(10);
        return base.MeasureOverride(constraint);
    }
}

/// <summary>
/// Logique d'interaction pour MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        DataContext = this;
    }

    public IEnumerable<Item> Items
    {
        get
        {
            for (int i = 0; i < 20; i++)
            {
                yield return new Item() { Index = i.ToString() };
            }
        }
    }
}

public class MyTreeView : TreeView
{
    protected override DependencyObject GetContainerForItemOverride()
    {
        return new MyTreeViewItem();
    }
}

public class MyTreeViewItem : TreeViewItem
{
    static MyTreeViewItem()
    {
        MyTreeViewItem.IsExpandedProperty.OverrideMetadata(typeof(MyTreeViewItem), new FrameworkPropertyMetadata(true));
    }

    public Size LastMeasure
    {
        get { return (Size)GetValue(LastMeasureProperty); }
        set { SetValue(LastMeasureProperty, value); }
    }

    // Using a DependencyProperty as the backing store for LastMeasure.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty LastMeasureProperty =
        DependencyProperty.Register("LastMeasure", typeof(Size), typeof(MyTreeViewItem), new UIPropertyMetadata(default(Size)));

    protected override Size MeasureOverride(Size constraint)
    {
        if (LastMeasure != default(Size))
        {
            if (this.VisualChildrenCount > 0)
            {
                UIElement visualChild = (UIElement)this.GetVisualChild(0);
                if (visualChild != null)
                {
                    visualChild.Measure(constraint);
                }
            }

            return LastMeasure;
        }
        else
        {
            LastMeasure = base.MeasureOverride(constraint);
            return LastMeasure;
        }
    }

    protected override DependencyObject GetContainerForItemOverride()
    {
        return new MyTreeViewItem();
    }
}

MainWindow.xaml

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:sys="clr-namespace:System;assembly=mscorlib"
    xmlns:local="clr-namespace:WpfApplication1"
    Title="MainWindow" Height="350" Width="525">

<Window.Resources>
    <HierarchicalDataTemplate DataType="{x:Type local:Item}" ItemsSource="{Binding Items}">
        <local:HeavyControl>
            <TextBlock FontSize="20" FontWeight="Bold" Text="{Binding Index}" />
        </local:HeavyControl>
    </HierarchicalDataTemplate>
</Window.Resources>

<local:MyTreeView x:Name="treeView" VirtualizingStackPanel.IsVirtualizing="True" ItemsSource="{Binding Items}" />

</Window>

person Sisyphe    schedule 26.10.2012    source источник
comment
Допустим, у меня есть произвольные сложные элементы управления в моих TreeViewItems, измерение которых может занять много времени — скажем, 10 мс —> и почему эти элементы управления требуют времени для измерения? Не проще ли оптимизировать это и решить проблему в корне?   -  person mathieu    schedule 26.10.2012
comment
К сожалению, это невозможно. Эти элементы управления были максимально оптимизированы, и некоторые из них продолжают занимать от 5 до 10 мс для измерения.   -  person Sisyphe    schedule 26.10.2012


Ответы (1)


Я бы попытался исправить ошибки во втором подходе.

  • Дайте каждому узлу/элементу дерева уникальный ключ
  • Иметь словарь, который содержит элементы управления в каждом узле/элементе
  • Во время прокрутки (или даже не в фокусе) просто текстовое/простое представление данных в дереве.
  • После того, как узел/элемент сфокусирован, замените простое представление соответствующими элементами управления из словаря, используя уникальный ключ.
  • Когда I узел/элемент теряет фокус, возвращайте его обратно к простому представлению, поскольку изменения все еще сохраняются в значении словаря, ссылающемся на элементы управления.
person Louis Ricci    schedule 26.10.2012