【发布时间】:2012-10-26 15:09:13
【问题描述】:
距离我上一个问题已经太久了,所以这里是一个新问题!
我在滚动期间遇到了虚拟化 WPF TreeView 的性能问题。假设我的 TreeViewItems 下有任意复杂的控件,这些控件可能需要很长时间(比如说 10 毫秒)才能进行测量。向下滚动 TreeView 很快就会变得非常缓慢,因为每个向下滚动单元都会调用 Layout pass。
在这种情况下,您对平滑滚动有何想法? 我尝试了两种不同的方法,但我都遇到了问题。
第一个是缓冲最后一个 MeasureOverride 结果并使用它而不是再次调用 MeasureOverride,除非大小发生变化。只要我在树中只有一个层次结构级别,它就可以工作。但是一旦我得到更多,一些项目就不会被渲染(尽管大小是正确保留的)。使缓冲区无效不会成为问题。
第二个是在滚动期间通过触发器替换 treeViewItems 下控件的 ControlTemplate,使用 ControlTemplate 非常快速地呈现。可悲的是,当我停止滚动已附加在逻辑树中的项目时,我遇到了奇怪的异常。
最后,我不在多线程环境中,因此无法使用异步绑定。 :(
这是我想要改进的树的一个非常小的示例(这是实现我的第一个想法的树),我只是将 Thread.Sleep(10) 放在用于呈现我的项目的 ContentControl 的 MeasureOverride 中。
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>
【问题讨论】:
-
“假设我的 TreeViewItems 下有任意复杂的控件,这些控件可能需要很长时间 - 比如说 10 毫秒 - 来测量” -> 为什么这些控件需要时间来测量?优化它并从根本上解决问题不是更简单吗?
-
不幸的是,这是不可能的。这些控件已尽可能优化,有些控件将继续花费大约 5 到 10 毫秒来测量。