基础内容

编写更高效的 ItemsControls

Charles Petzold

代码下载可从 MSDN 代码库
浏览代码联机

内容

一个 ItemsControl 散点图绘图
性能 Disappointment
隐藏的循环
使用值转换器
监视出 Freezables !
中间的演示者
自定义的数据元素
biting the Bullet
DrawingVisual 解决方案

当 DataTemplate 的真正功能突然变成很明显,有提供生命周期中的每个 Windows Presentation Foundation (WPF) 程序员次。此 epiphany 通常伴随该的实现"您好,我可以使用一个 DataTemplate 来创建条形图或一个散点图与几乎没有编码的绘图"

在 DataTemplate 通常在一起的 ItemsControl 或从包括列表框,ComboBox,菜单,TreeView,工具栏,StatusBar 的 ItemsControl 派生的类中创建的短,保持项的集合的所有控件。DataTemplate 定义集合中的每个项的显示方式。DataTemplate 包含主要的一个或多个元素的可视树与链接与这些元素的属性集合中的项的数据绑定。如果集合中的项目 (通常通过实现 INotifyPropertyChanged 接口) 实现某种类型的属性更改通知,项目-控件可以动态地响应在项目的更改。

在 disappointment 可能有点以后会。如果您要显示大量数据时,可能会发现 ItemsControl 和 DataTemplate 不扩展和。此列将是关于内容如何对付这些性能问题。

一个 ItemsControl 散点图绘图

让我们从一个 ItemsControl 和在 DataTemplate 中创建散点图绘图。第一步是创建业务对象表示数据项目。图 1 显示了一个具有通用名称 DataPoint 稍有 abridged 的) 的简单类。DataPoint 实现 INotify­PropertyChanged 接口,这意味着它包含名为 PropertyChanged 属性已更改时,该对象触发的事件。

图 1 </a0>-DataPoint 类表示一个数据项

public class DataPoint : INotifyPropertyChanged
{
    int _type;
    double _variableX, _variableY;
    string _id;

    public event PropertyChangedEventHandler PropertyChanged;

    public int Type
    {
        set
        {
            if (_type != value)
            {
                _type = value;
                OnPropertyChanged("Type");
            }
        }
        get { return _type; }
    }

    public double VariableX [...]
    public double VariableY [...]
    public string ID [...]

    protected virtual void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

在 VariableX 和 VariableY 属性指示笛卡尔坐标系统中的点的位置。 (为该列中,在值范围为从 0 到 1)。 Type 属性用于组数据点 (它将具有从 0 到 5 的值,用于显示六种不同的颜色中的信息),ID 属性标识每个点用文本字符串。

在实际的应用程序中以下 DataCollection 类可能包含更多的属性虽然但例如它有如只有一个,数据点数类型 ObservableCol­lection <datapoint> 的:

public class DataCollection
{
    public DataCollection(int numPoints)
    {
        DataPoints = new ObservableCollection<DataPoint>();
        new DataRandomizer<DataPoint>(DataPoints, numPoints, 
                                          Math.Min(1, numPoints / 100));
    }

    public ObservableCollection<DataPoint> DataPoints { set; get; }
}

ObservableCollection 具有 CollectionChanged 属性添加到项目或将其从集合中删除时触发的。

此特定的 DataCollection 类使用一个名为 DataPointRandomizer 生成用于测试目的的随机数据的类创建所有数据项在其构造函数中。 DataPointRandomizer 对象还可以设置计时器。 秒的每个十分位该计时器的时间刻度方法都将更改 VariableX 或 VariableY 属性在点的 1%。 因此,平均,所有点都更改每 10 秒钟。

现在让我们编写在散点图绘图中显示此数据的一些 XAML。 图 2 显示了包含一个 ItemsControl UserControl。 将类型 DataCollection 的对象的代码中设置该控件的 DataContext。 ItemsControl 将 ItemsSource 属性绑定到该 DataCollection 表示 ItemsControl 都将用类型 DataPoint 的项目来填充该数据点数属性。

图 2 DataDisplay1Control.xaml 文件

<UserControl x:Class="DataDisplay.DataDisplayControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:data="clr-namespace:DataLibrary;assembly=DataLibrary">

    <ItemsControl ItemsSource="{Binding DataPoints}">
        <ItemsControl.ItemTemplate>
            <DataTemplate DataType="data:DataPoint">
                <Path>
                    <Path.Data>
                        <EllipseGeometry RadiusX="0.003" RadiusY="0.003">
                            <EllipseGeometry.Transform>
                                <TranslateTransform X="{Binding VariableX}" 
                                                    Y="{Binding VariableY}" />
                            </EllipseGeometry.Transform>
                        </EllipseGeometry>
                    </Path.Data>

                    <Path.Style>
                        <Style TargetType="Path">
                            <Setter Property="Fill" Value="Red" />
                            <Style.Triggers>
                                <DataTrigger Binding="{Binding Type}" Value="1">
                                    <Setter Property="Fill" Value="Yellow" />
                                </DataTrigger>
                                <DataTrigger Binding="{Binding Type}" Value="2">
                                    <Setter Property="Fill" Value="Green" />
                                </DataTrigger>
                                <DataTrigger Binding="{Binding Type}" Value="3">
                                    <Setter Property="Fill" Value="Cyan" />
                                </DataTrigger>
                                <DataTrigger Binding="{Binding Type}" Value="4">
                                    <Setter Property="Fill" Value="Blue" />
                                </DataTrigger>
                                <DataTrigger Binding="{Binding Type}" Value="5">
                                    <Setter Property="Fill" Value="Magenta" />
                                </DataTrigger>
                            </Style.Triggers>
                        </Style>
                    </Path.Style>

                    <Path.ToolTip>
                        <StackPanel Orientation="Horizontal">
                            <TextBlock Text="{Binding ID}" />
                            <TextBlock Text=", X=" />
                            <TextBlock Text="{Binding VariableX}" />
                            <TextBlock Text=", Y=" />
                            <TextBlock Text="{Binding VariableY}" />
                        </StackPanel>
                    </Path.ToolTip>
                </Path>
            </DataTemplate>
        </ItemsControl.ItemTemplate>

        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <Grid Background="Azure" 
                      LayoutTransform="300 0 0 300 0 0" 
                      IsItemsHost="True" />
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
    </ItemsControl>
</UserControl>

ItemsControl 的 ItemTemplate 属性的定义每个项目显示的可视树的一个 DataTemplate 为。 此树包含的数据属性设置为一个 EllipseGeometry 只是一个路径元素。 很实际上只是一个小圆点),我的含义非常少的点) 与 0.003 单位的半径。 但并不为小为似乎由于该 ItemPanel 属性 ItemsControl 的设置为一个 ItemsPanelTemplate 包含单个单元格网格显示所有点。 此网格提供它按比例调整的 300,使数据点几乎 1 单元半径范围中的一个 LayoutTransform。

此付息日不固定的操作被批准 VariableX 和 VariableY 属性一个 TranslateTransform 的 X 和 Y 属性的数据绑定的。 VariableX 和 VariableY 属性仅介于 0 到 1,所以需要增加在网格以占据屏幕 300 个单位方块上的区域的大小。

路径元素的填充属性设置为基于每个 DataPoint 对象中类型属性的值的画笔样式中。 Path 对象将同时分配显示有关每个数据点信息的工具提示。

性能 Disappointment

本专栏附带的源代码包含一个 Visual Studio 解决方案包含七个项目: 六个应用程序和一个库。 名为 DataLibrary 库包含大部分共享代码包括 DataPoint、 DataCollection 和 DataPointRandomizer。

第一个应用程序项目命名为 DataDisplay1。 它将包含一个 MainWindow 类,其他应用程序和 图 2 所示 DataDisplay1Control.xaml 文件共享。 MainWindow 访问用于显示散点图绘制该控件,但还包括一个 TextBox 输入一个的项目计数按钮以开始创建收集和 culminate 图表的显示中的进程的三个阶段中显示已用时间的 TextBlock 对象。

默认情况下的项目数是 1,000,并且该程序似乎可以正常工作。 将项目数设置为 10,000,但还有中显示 图 3 ,它还演示了非常生成数据的 artificiality 在显示之前延迟。

fig03.gif

图 3 </a0>-DataDisplay1 显示

将显示三个已用时间。 当您单击按钮时,MainWindow.xaml.cs 中的单击处理程序会首先使用指定的数据点个数创建 DataCollection 类型的对象。 第一个经过的时间是该集合的创建。 集合已设置为窗口的 DataContext。 这是第二个经过的时间。 第三个的时间是 ItemsControl 来显示结果的数据点所需时间。 若要计算此第三个的运行时间,我使用的一个更好的替代方式的缺少 LayoutUpdated 事件。

可以看到大部分时间包括更新显示 ; 在我的计算机上的三个试验平均值 7.7 秒。 这是而干扰,特别考虑此程序确实有什么不对它。 它它们预期的方式使用 WPF 功能。 内容完全即将呢?

当程序 DataPointCollection 类型的对象设置该 DataContext 属性时,ItemsControl 将收到 ItemsSource 属性的属性更改通知。 ItemsControl 枚举 DataPoint 的对象的集合,然后为每个 DataPoint 对象创建一个 ContentPresenter 对象。 (ContentPresenter 类从 FrameworkElement ; 这是 ContentControl 派生产品,如按钮和窗口用于显示控件内容的相同元素)。

对于每个 ContentPresenter 对象内容的属性设置为相应的 DataPoint 对象,并将 ContentTemplate 属性设置 ItemsControl 的 ItemTemplate 属性。 这些 ContentPresenter 对象然后添加到使用 ItemsControl 显示项面板此案例单个单元格网格中。

过程的这一部分是非常快速。 ItemsControl 必须显示时,将提供耗时的部分。 因为面板积累了新的子项,则称为其 MeasureOverride 方法,而且它是此调用所需 7.7 秒执行的 10,000 个项目。

面板的 MeasureOverride 方法调用每个 ContentPresenter 子项的度量值方法。 如果 ContentPresenter 子还没有创建要显示其内容的可视树,它必须立即这样做。 ContentPresenter 创建基于模板的 ContentTemplate 属性中存储此可视树。 ContentPresenter 还必须设置在中的可视树元素的属性和存储在其内容的属性 (在本例中 DataPoint 对象) 中的对象的属性之间的数据绑定。 ContentPresenter 然后调用此 Visual 树的根元素的度量值方法。

如果您感兴趣探索此过程详细 DataLibrary DLL 将包含使您的名为的 SingleCellGrid 探测面板中的一个类。 在 DataDisplay1Control.xaml 文件中可以用数据: SingleCellGrid 只替换网格。

ListBox 包含多个项目,但显示只有几时, 它会忽略此初始工作大量,因为默认情况下,它使用一个 VirtualizingStackPanel 只按照都被显示创建孩子的。 这不可能使用一个散点图绘图但是。

隐藏的循环

循环是计算机编程的基础的构造。 我们使用计算机,唯一原因是编写执行重复性 chores 的循环。 但循环似乎从我们的编程体验会消失。 功能编程语言如 F #,循环 relegated 旧式编程并经常替换整个数组、 列表和集上工作的操作。 同样,在 LINQ 查询运算符执行操作集合上没有显式循环。

这种移动离开显式循环不是只编程样式,但与计算机硬件更改保持速度的基本 evolutionary 开发中更改。 新计算机今天通常有两个或四个处理器,在继续在年我们可能会看到计算机的处理器并行执行的几百个。 编程语言或开发框架中后台处理的循环更轻松地可以利用并行处理不由程序员任何特殊工作。

之前的强大的未来但是,我们必须保持注意该循环仍存在即使我们不能看到这些,并且作为方面程序员,一次记录,"ain ' 存在 t 为可用的循环的类事情"。

在隐藏的循环的 ItemsControl ItemTemplate 部分中定义的 DataTemplate。 DataTemplate 会调用可能会有成千上万的元素和其他的对象,以及建立数据绑定的创建。

如果我们确实有代码循环,我们所有可能会更仔细设计的 DataTemplate。 由于对性能的影响,微调 DataTemplate 是值得时间和精力。 几乎任何您对该操作可能会对性能有明显的影响。 通常,只是程度影响 (和哪个方向) 可能很难预测,因此您可能会希望尝试使用多种方法。

一般情况下,您需要简化在 DataTemplate 中的可视树。 尝试最小化元素、 对象和数据绑定的数目。

请转到 DataDisplay1Control.xaml,并尝试删除对 TextBlock 项目在工具提示中的数据绑定。 (您可以只需插入左的大括号的前面的任何字符)。 您应 shave 秒关闭 7.7 秒的上一个时间的两个 tenths。

现在注释掉整个工具提示部分,并且您将看到显示时间拖放以 4.7 秒关闭。 设置路径元素某些颜色,和注释掉整个样式部分中的 Fill 属性,并显示时间到 3.5 秒丢弃的现在。 删除在路径元素的转换,并会到大约 1 秒。

当然,现在很 worthless 因为它不显示在的数据,但您可能可以看到如何可以开始一个外观获得这些项目的影响。 只需少量标记,但值得大量实验。

下面是不 hurting 功能提高了同时性能和可读性的更改: path.ToolTip 标记的内容替换为以下:

<TextBlock>
    <TextBlock.Text>
        <MultiBinding StringFormat="{}{0}, X={1}, Y={2}">
            <Binding Path="ID" />
            <Binding Path="VariableX}" />
            <Binding Path="VariableY}" />
        </MultiBinding>
    </TextBlock.Text>
</TextBlock>

上绑定 StringFormat 选项中是新增.NET 3.5 SP 1,并使用此处获取显示时间下从 7.7 秒到 6.4。

使用值转换器

数据绑定 (可选) 可以引用小调用实现 IValueConverter 或 IMultiValueConverter 接口的值转换器的类。 名为值转换器中的方法转换,并 ConvertBack 执行绑定源和目标之间的数据转换。

转换器的通常通常都是适用于各种应用程序 ; 一个示例很方便的 BooleanToVisibilityConverter 用来将 True 和 False 为 visibility.visible 和 visibility.collapsed,分别。 但转换器可为特别在需要它们。

若要为了简化 DataTemplate,并减少的数据绑定,可以创建两个转换器。 图 4 所示,转换器将名为 IndexToBrushConverter (包含在 DataLibrary DLL),并且一个非负整数转换为一个画笔。 转换器具有名为类型画笔数组的画笔的公共属性,则整数只是该数组索引。

图 4 IndexToBrushConverter 类

public class IndexToBrushConverter : IValueConverter
{
    public Brush[] Brushes { get; set; }

    public object Convert(object value, Type targetType, 
                          object parameter, CultureInfo culture)
    {
        return Brushes[(int)value];
    }

    public object ConvertBack(object value, Type targetType, 
                              object parameter, CultureInfo culture)
    {
        return null;
    }
}

XAML 文件的资源节中, 转换器可以实例化的 x: 数组组就像这样:

<data:IndexToBrushConverter x:Key="indexToBrush">
    <data:IndexToBrushConverter.Brushes>
        <x:Array Type="Brush">
            <x:Static Member="Brushes.Red" />
            <x:Static Member="Brushes.Yellow" />
            <x:Static Member="Brushes.Green" />
            <x:Static Member="Brushes.Cyan" />
            <x:Static Member="Brushes.Blue" />
            <x:Static Member="Brushes.Magenta" />
        </x:Array>
    </data:IndexToBrushConverter.Brushes>
</data:IndexToBrushConverter>

然后可以替换整个样式为中显示的部分 图 2 具有一个绑定的引用此转换器:

<Path Fill="{Binding Type, Converter={StaticResource indexToBrush}}">

第二个转换器 DataLibrary 中的称为 DoublesToPointConverter。 此转换器实现 IMultiValueConverter 接口来创建从两个 double X 和 Y 的点。 请注意使用此转换器允许直接,设置 ellipse­geometry 该中心属性无需在 TranslateTransform。

DataDisplay2 项目包括这些转换器和使用工具提示,StringFormat 方法但该时间 disappointing: 7.7 秒的工具提示不,4.4 秒,并与某个常量 3.4 填充画笔。

通常,我希望转换器以提高性能。 我不确定这不此处,大小写,但我将吃惊的一个装箱和取消装箱的问题的原因。 在该的 DoublesToPointConverter 是例如传入的双精度值必须装箱并取消装箱,而且传出点进行装箱和取消装箱。

监视出 Freezables !

如果希望真正看到 DataDisplay2 的性能降低,请尝试替换这些画笔中的 x: 静态成员:

<x:Static Member="Brushes.Red" />

使用 SolidColorBrush 对象:

<SolidColorBrush Color="Red" />

为 20 秒和以后,显示时间 leaps 最 ! 但在 x: 静态和 SolidColorBrush 元素好像几乎相同。 用颜色设置为红色则该静态 Brushes.Red 属性将返回一个 SolidColorBrush。

但请记住从 Freezable 派生的 SolidColorBrush。 在 Brushes.Red 属性返回一个已冻结的 SolidColorBrush 和线程安全。 值不能更改。 此画笔传递到 Visual 复合系统时, 它可以视为一个常量。

在显式的 SolidColorBrush 但是,不冻结。 它保持活动 Visual 复合系统中,并将响应的 Color 属性中的更改。 该动态 potentiality 复杂系统处理,并且的性能下降中显示自身。

因此,请注意您应该冻结不再能更改任何 freezable 对象。 在代码,调用冻结方法 ; XAML 中, 可以使用 PresentationOptions:freeze 选项。 通常您会发现不同之处会可以忽略,但 10,000 个图形对象中的未冻结画笔的使用很明显的一个重要因素。

中间的演示者

在许多常见的应用程序结构是中介的自定义将某种类型的实际的业务对象在用户界面之间 (在此示例,DataPoint 和 DataCollection)。 出的相对对于传统,我将调用此中间"演示者"(尽管传统的演示者执行而不是多个,我将显示以下)。

此演示者的角色之一可能是使从业务对象的信息更易于与绑定到用户界面。 是例如,在 DataPoint 业务对象有名为 VariableX 和类型 Double 类型的值的 VariableY 的属性。 在 DataPointPresenter 类 (如我会调用它) 可能而属性命名的类型为 Point 的变量。

为了性能我编写了 DataPointPresenter DataPoint 中派生。 除了该变量的属性还定义名为画笔 (的类型画笔) 和工具提示类型为 String) 的属性。 图 5 显示了 DataPointPresenter 类的轻的缩写版本。

图 5 </a0>-DataPointPresenter 类

public class DataPointPresenter : DataPoint
{
    static readonly Brush[] _brushes = 
    { 
        Brushes.Red, Brushes.Yellow, Brushes.Green, 
        Brushes.Cyan, Brushes.Blue, Brushes.Magenta 
    };
    Point _variable;
    Brush _brush;
    string _tooltip;

    public Point Variable [...]
    public Brush Brush [...]
    public string ToolTip [...]

    protected override void OnPropertyChanged(string propertyName)
    {
        switch (propertyName)
        {
            case "VariableX":
            case "VariableY":
                Variable = new Point(VariableX, VariableY);
                goto case "ID";

            case "ID":
                ToolTip = String.Format("{0}, X={1}, Y={2}",
                                        ID, VariableX, VariableY);
                break;

            case "Type":
                Brush = _brushes[Type];
                break;
        }
        base.OnPropertyChanged(propertyName);
    }
}

DataLibrary DLL 还包含一个 DataCollectionPresenter 类,但它会保持 DataPointPresenter 对象的集合与 DataCollection 相同的。

DataDisplay3 项目包含这些表示器类。 整个 DataTemplate 如下所示:

<DataTemplate DataType="data:DataPointPresenter">
    <Path Fill="{Binding Brush}"
          ToolTip="{Binding ToolTip}">
        <Path.Data>
            <EllipseGeometry Center="{Binding Variable}"
                             RadiusX="0.003" RadiusY="0.003" />
        </Path.Data>
    </Path>
</DataTemplate>

此方法好得比在种转换器将显示时间下的所有功能,包括工具提示的 3.3 秒。 大的缺点),您可能会避免它关闭或考虑严重的 deficiency) 是不同的画笔现在是 DataPointPresenter 类中的硬编码。 这不是最佳的。 在 WPF 中编程它很始终好才能继续以微调 XAML 同时关闭代码文件。

XAML 文件中拥有这些画笔是首选的尤其是如果类型的值过超过数字 5 增加。 一种方法可能是在应用程序的 XAML 文件的资源部分中存储的画笔数组并让该第一个的实例在 DataPointPresenter 访问它们,并将它们存储在一个静态变量中。 既然您已了解在本示例演示者多少影响,我不能中使用其余的方法。

自定义的数据元素

散点图绘图中绘制小点,我已经被使用 Path 元素其数据属性,设置为一个 EllipseGeometry。 因为 EllipseGeometry 具有类型为 Point 的中心属性,我所必须创建一个转换器或演示者类型 Double 类型的值的两个属性中获得一个 Point 对象。

另一个解决方案是此路径和 EllipseGeometry 组合替换自定义绘制一个圆点的 FrameworkElement 派生。 由于我们编写其自己,它可以具有指向的 CenterX 和 CenterY 的属性,而不是类型的一个中心属性 Double 类型。 而不是类型画笔的填充属性中,,它可以具有索引类型画笔数组的画笔属性的类型 int 的 FillIndex 属性。

(在 DataLibrary 项目中称为 DataDot) 类是相当简单,包含主要依赖关系属性和换行这些依赖关系属性的 CLR 属性。 MeasureOverride 包含一行:

return new Size(CenterX + RadiusX, CenterY + RadiusY);

OnRender 是几乎一样简单:

if (Brushes != null)
    dc.DrawEllipse(Brushes[FillIndex], null, 
                   new Point(CenterX, CenterY), 
                   RadiusX, RadiusY);

在 DataDisplay4 项目 DataDot 显示如下 DataTemplate:

<data:DataDot CenterX="{Binding VariableX}" 
              CenterY="{Binding VariableY}"
              Brushes="{StaticResource brushes}"
              FillIndex="{Binding Type}"
              RadiusX="0.003" RadiusY="0.003">

画笔资源参数引用包含六种颜色的 x: 数组元素。

在工具提示的 3.3 秒内 DataDisplay4 项目使用此自定义的元素的就可以显示本身并不 2.5 秒,这极好的性能改进考虑 DataDot 类的简单特性。

biting the Bullet

如果已经去除下到的元素和对象 tiniest 数 DataTemplate 并减少数据绑定,最小可能,做其他任何内容您可以认为性能是仍然远远不够的也许是 bite the bullet 的时间。

并通过,我意味着,到确认一个 ItemsControl 和一个 DataTemplate 的组合确实强大但可能不完全解决方案您需要为此特定的应用程序的可能。 此确认不会构成的 WPF 的 abandonment: 您仍要使用 WPF,才能实现解决方案。 是只是 behind-the-scenes 创建 10,000 个 FrameworkElement 派生产品可能不是最有效使用资源的识别。 此种方法是执行整个类事情的自定义 FrameworkElement 派生) 一个元素,而不是 10,000 个元素。

DataLibrary DLL 中的 ScatterPlotRender 类派生自 FrameworkElement 并有三个依赖属性: 类型 ObservableNotifiableCollection <datapoint>,类型画笔数组的画笔和类型画笔的背景的 ItemsSource。

我的列中,可能会记住 ObservableNotifiableCollection 类" 依赖关系属性和通知"在 2008 年 9 发放的 MSDN Magazine。 此类要求其成员的实现 INotifyPropertyChanged 接口。 类可触发的事件,不仅时添加或删除集合中,但当集合中的对象的属性更改时对象。 这是针对如何 ScatterPlotRender 将预警 DataPoint 对象的该 VariableX 和 VariableY 属性更改时。

ScatterPlotRender 类处理方式非常简单的所有这些事件。 每当在 ItemsSource 属性更改或更改集合或集合中 DataPoint 对象的属性更改,ScatterPlotRender 调用 InvalidateVisual。 这将生成其绘制整个散点图绘图的 OnRender 调用。 代码如 图 6 所示。

图 6 OnRender

protected override void OnRender(DrawingContext dc)
{
    dc.DrawRectangle(Background, null, new Rect(RenderSize));

    if (ItemsSource == null || Brushes == null)
        return;

    foreach (DataPoint dataPoint in ItemsSource)
    {
        dc.DrawEllipse(Brushes[dataPoint.Type], null, 
            new Point(RenderSize.Width * dataPoint.VariableX,
                      RenderSize.Height * dataPoint.VariableY), 1, 1);
    }
}

请注意 VariableX 和 VariableY 值将乘以在宽度和高度该元素可能会将其设置 XAML 文件中。 DataDisplay5 项目有实例化一个 ScatterPlotRender 对象的 XAML 文件如下:

<data:ScatterPlotRender Width="300" Height="300"
                        Background="Azure" 
                        ItemsSource="{Binding DataPoints}"
                        Brushes="{StaticResource brushes}" />

画笔资源键引用冻结 SolidColor­brush 对象的数组。

好消息是此散点图绘图弹出屏幕上非常快速地。 绘图的 OnRender 方法是在最快的方法,可以直观地显示在 WPF 元素。 坏消息是元素将继续 VariableX 或 VariableY 属性更改,而这会发生第二个的每个十分位时完全刷新其自身。 早期版本更新自身 (在我的计算机) 使用大约 10%的 CPU 时间。 30%区域中,此可正在运行。 如果应用程序显示经常更新的数据,您可能需要调整执行 (进入下一个最) 的绘制方式。

其他大缺点是没有现在没有工具提示。 工具提示不可能在此类中, 但相当复杂。 我的下一版本中需要工具提示。

DrawingVisual 解决方案

通常从 FrameworkElement 派生的类在 OnRender 方法中绘制本身,但也可以通过维护 Visual 子项的集合必须可视外观。 这些子项显示之上任何 OnRender 方法用于画。

Visual 子项可以是任何从 Visual,派生的其中包括 FrameworkElement 和控件,但 FrameworkElement 派生也可以创建 comparatively 轻型 Visual 子项 DrawingVisual 对象的形式。

如果 FrameworkElement 派生创建 DrawingVisual 对象,将通常存储其处理某些维护 Visual 子项开销 VisualChildren 集合中。 类仍然需要覆盖 VisualChildrenCount GetVisualChild,但是。

通过为每一个 DataPoint 中创建一个 DrawingVisual 对象 ScatterPlotVisual 类工作。 DataPoint 对象的属性更改时, 类只需更改与该 DataPoint 的 DrawingVisual。

像在 ScatterPlotRender 类 ScatterPlotVisual 类定义了依赖关系属性名为 ItemsSource、 画笔和背景。 它还维护一个 VisualChildren 集合,它必须保持同步 ItemsSource 集合。 如果某个项目添加到 ItemsSource 集合,新的 Visual 必须添加到 VisualChildren 集合中。 如果从 ItemsSource 集合中删除某个项目,对应的 Visual 必须删除从 VisualChildren 集合中。 如果 ItemsSource 集合中的项目在 VariableX 或 VariableY 属性更改,必须更改 VisualChildren 集合中相应的项。

若要帮助此同步 VisualChildren 集合不实际存储类型 DrawingVisual 的对象,但而维护类型定义如下 ScatterPlotVisual 类内部的 DrawingVisualPlus 的对象:

class DrawingVisualPlus : DrawingVisual
{
    public DataPoint DataPoint { get; set; }
}

此附加属性便于查找对应于特定的 DataPoint 对象 VisualChildren 集合中的特定 DrawingVisualPlus 对象。

实现此方法的 DataDisplay6 项目是最佳的总体方法。 我确信稍有大于 DataDisplay5,启动创建时间,但很只明显且更新开销会显著减少。

如果跳最多 100,000 个数据点,但,WPF 再次 buckles 在紧张下中。 在此级别的图形输出,我担心我已用尽建议。 (可能获得更快的计算机?)

将您的问题和提出的意见发送至 mmnet30@Microsoft.com.

Charles Petzold 是 longtime 到 MSDN Magazine 特约编辑。 他最新的著作是 The Annotated Turing: A Guided Tour through Alan Turing's Historic Paper on Computability and the Turing Machine (Wiley,2008)。 他的网站是 www.charlespetzold.com.