WPF更新元素时的性能表现

4

我目前正在开发一个GUI,用于Ising模型(德语维基百科,因为只有右侧的图片真正重要),该模型应由约200x200个自旋元素组成。 我是这样实现它的:

<UniformGrid Name="grid" .... />

在代码中,我为每个旋转添加了一个矩形,并在后台更新,以便更改旋转的值。但这种方法运行非常缓慢,因此我进行了更改,使用了绑定(Binding)。

 <ItemsControl Name="IsingLattice" ItemsSource="{Binding Spins}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <UniformGrid Name="grid" ...
            ...
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Grid>
                <Rectangle Fill={Binding Color} ...

但这个方法非常慢。我已经尝试了三天来调试和改进它,但迄今为止没有成功。
现在的问题是:我的方法错了吗?如果是,我应该使用什么代替?如果不是,我该如何提高性能?
如果相关,我将更新此帖子,介绍我模型实现的一些细节。
编辑:可以通过与元素交互来更改单个旋转。虽然可能不那么难,但可以通过在实际图形上方放置一个透明层来完成。

性能在10x10的旋转中表现良好,但在30x30元素中变得太慢。 - oerpli
200x200,也就是40,000个像素,能在屏幕上全部显示出来,对吧? - kennyzx
@oerpli:获取一些性能分析数据可能会很有趣。看看这个链接 https://msdn.microsoft.com/zh-cn/library/aa969767(v=vs.110).aspx 和这个链接 http://www.codeproject.com/Articles/784529/Solutions-for-WPF-Performance-Issue - bic
@kennyzx 是的,它是一个600像素的正方形 - 所以对于200x200,每个旋转是9像素宽(假设WPF将像素映射到像素上,我不知道)。 - oerpli
@bic 如果分析器告诉我任何有趣的东西,我会运行它。 - oerpli
你尝试过为元素使用固定的高度/宽度吗?你的需求可能不同。但是,根据你使用的控件,它们会相对于父/子元素调整大小,在你的情况下可能会导致性能问题。然而,Steven的答案一旦完全实现将会提供出色的性能。 - Dbl
4个回答

6
你可以编写一个自定义元素(派生自FrameworkElement),将旋转数据存储在内部,然后通过重写OnRender方法一次性呈现数据。
public sealed class IsingModel : FrameworkElement
{
    readonly bool[] _spinData = new bool[200 * 200];

    protected override void OnRender(DrawingContext dc)
    {
        // use methods of DrawingContext to draw appropriate
        // filled squares based on stored spin data
    }

    protected override void OnMouseDown(MouseButtonEventArgs e)
    {
        base.OnMouseDown(e);

        // work out which "cell" was clicked on and change
        // appropriate spin state value in Boolean array

        InvalidateVisual(); // force OnRender() call
    }
}

这种方法应该比拥有数千个单独元素的方法更快。具体有多快我不知道。

谢谢你的回答 - 我今天实现了它并进行了一些测试 - 在90x90的格点上性能良好,甚至在120x120的情况下也还可以 - 已经有了巨大的改进。 - oerpli
注意:对于不会更改的资源,例如 ColorBrushPen 对象,冻结它们非常重要。 - oerpli
除非控件的大小发生了变化,否则不应使用InvalidateVisual()。如果您只想更新可视化,请在依赖属性上使用AffectsRender,或者在OnRender()期间将DrawingGroup放入可视树中,然后稍后使用DrawingGroup.Open()进行更新。 - David Jeske

4

ItemsControl旨在根据数据源重复UI控件。UI控件必须是可定制的,当布局更改时响应并且交互式。这两者都不符合您的情况。实际上,您只想渲染一张图片。

这个 - 似乎是一个位图,因此您应该将其视为位图。使用Image代替ItemsControl,并使用WritableBitmap作为Image的Source。

至于您的原始代码,它可能有几个性能瓶颈:

  1. 您用作ItemsSource的类的生成可能需要一些时间
  2. UniformGrid需要测量每个元素的大小并计算位置。这可能需要一段时间。使用Canvas可以获得更好的结果
  3. ItemsControl从DataTemplate生成40,000个项可能需要一些时间。您可以手动创建这些矩形并在代码中将其添加到画布中
  4. 绑定具有性能成本。如果您的每个项目都是数据绑定的,则需要评估40,000次绑定。您可以在代码中手动设置属性而不是绑定。

使用canvas和无绑定的方法,我能够在仅500毫秒内渲染网格。但是,使用WritableBitmap或其他“像素为基础”的方法,您可以显示更大的网格。


我提到了其他的“基于像素”的方法,例如可能是 @Steven Rands 的答案。 - Liero
我现在使用可写位图实现了它,而且速度非常快(尽管我不得不使用不安全的代码进行更新)。现在已经足够快,可以处理600x600的网格。 - oerpli
2
我很高兴它对你有用。如果需要,你可以通过将每个旋转映射到可写位图中的单个像素来进一步提高性能。然后根据需要设置图像的拉伸。例如 [Image Height="600" Width="600" Strech="Uniform"]拉伸是在图形卡上完成的,速度更快。并且你可以拥有响应式布局。 - Liero

0

无论是WPF还是Windows Forms或其他任何GUI技术,它们并不适用于处理较重的图形。它们的设计初衷是用于开发简单的图形界面。

如果你需要处理图形的强大能力(例如动态更新40000个单元格),那么你需要一个专门的图形框架。最好选择一个适合自己的游戏框架。

或者,你可以尝试通过绑定到一张单一的图片,并在单元格发生变化时自己绘制该图片来实现模拟。也许这已经足够了,你需要进行测试。


这是“重型图形”吗:http://i.imgur.com/CYb08qT.png 我还想在单击时更改单个旋转,目前的实现非常容易(我只需更改发送者的DataContext)-您有任何建议哪个图形框架可以实现这一点? - oerpli
@oerpli 不,那只是简单的图形。当你想根据时间更新它时,它就变成了重度图形。我没有使用C#框架的经验,SFML非常好,我听说它有一个C#包装器。但正如我所说,也许先将其绘制到图片上已经足够了。 - nvoigt

0
你有没有考虑使用BitmapCache来提高渲染速度?
我的理解是,当绘制复杂控件或同时在屏幕上拥有多个控件实例时,这可以显著提高渲染速度。你需要在网格级别启用缓存,而不是在每个单独的微调器上启用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接