【问题标题】:How do I keep the UI responsive when adding many UIElements to a canvas?向画布添加许多 UIElement 时如何保持 UI 响应?
【发布时间】:2014-09-14 21:12:33
【问题描述】:

我有多达 70000 个形状派生对象要添加到画布中。显然这会导致 UI 无响应,在我的情况下大约 20 秒。我已经尝试使用 Dispatcher 方法来尝试克服这个问题,但我仍然遇到问题。

我实现它的第一种方式是这样的:

foreach (var shape in ShapeList)
{
    Dispatcher.BeginInvoke(DispatcherPriority.Input, new Action(() =>
            {
                drawingCanvas.Children.Add(shape);
            }));
}

这确实使 UI 具有响应性,但不利的一面是,它需要很长时间(比如 10 分钟?)才能完全添加所有形状。它还会在添加形状时立即显示形状。

我实现它的第二种方式是:

Dispatcher.BeginInvoke(DispatcherPriority.Input, new Action(() =>
            {
                foreach (var shape in ShapeList)
                {
                    drawingCanvas.Children.Add(shape);
                }
            }));

这似乎与根本不使用调度程序具有相同的效果。

我已经在单独的线程上尝试了这两种实现,并且与主线程没有区别。此外,我必须将它们全部添加为单独的 Shape 对象,因为它们中的每一个都必须支持单独的 MouseClickEvents,因此我没有使用 drawingVisual。

那么有什么方法可以让主窗口保持响应,同时保持添加过程相对较快?

谢谢。

*编辑 我知道 CanvasVirtualization 但目前我理解起来似乎有点复杂。有没有其他方法可以使用?

【问题讨论】:

  • 70,000 是放在 Canvas 对象上的很多东西。如果这些对象是只读的/非交互的,你会考虑使用类似 WriteableBitmap 的东西吗?
  • Adding them on another thread 听起来是个好主意,我无法让它发挥作用,也许你可以。
  • 我遇到了同样的问题。您使用 Dispatcher 的方法是正确的,也是唯一的方法,因为您必须在 UI 线程上创建 UI 元素。恐怕对于这么多的对象,您将不得不使用画布虚拟化。但是,对我有用的方法是使用 ItemsControl 为 Canvas 定义模板,并用视图模型表示我的形状,我将其放入集合中并绑定到 ItemsControl。然后我使用模板来定义形状的外观。我不知道为什么,但是这种方法给了我比向 Canvas.Children 添加项目更好的性能。如果您想尝试,我可以发布代码。
  • @McGarnagle:所有对象都是交互式的。它们都可以被选中并附加一个 mousebuttondown 事件处理程序。在这种情况下是否仍然可以使用可写位图?
  • 我猜不是,如果它们是交互式的。而且我想我可能是错的,70,000 并不是所有那么多...但我仍然认为您最终可能不得不进行某种优化,例如虚拟画布...

标签: c# wpf performance user-interface wpf-controls


【解决方案1】:

您提到的使用 Dispatcher 的两个问题相当容易解决或缓解。

  1. 急剧减速(20 秒 -> 10 分钟),并且
  2. 形状逐渐添加

对于 1),您可以通过分批添加项目而不是一次添加项目来加快流程,同时仍保持响应能力。对于 2),一个简单的解决方案就是隐藏画布,并在完成绘图时显示它。

所以是这样的:

// group shapes into batches
const int batchSize = 100;
var batches = ShapeList.Select((item, index) => new { index = index, item = item })
    .GroupBy(item => item.index / batchSize)
    .ToArray();

// function to create a local closure for each batch
Action<int> addBatch = batchNumber => {
    var batch = batches[batchNumber];
    Dispatcher.BeginInvoke(DispatcherPriority.Input, new Action(() =>
    {
        foreach (var shape in batch)
        {
            drawingCanvas.Children.Add(shape.item);
        };
        if (batchNumber >= batches.Length - 1)
            drawingCanvas.Visibility = Visibility.Visible;
    }));
};

drawingCanvas.Visibility = Visibility.Collapsed;
for (int i=0 ; i < batches.Length ; i++)
{
    addBatch(i);
}

【讨论】:

  • 我一定会试试这个。我之前确实尝试过隐藏画布,但我使用了 Visibility.Hidden。直到现在我才真正知道 Visibility.Collapsed 是什么。
【解决方案2】:

理想情况下,您应该使用虚拟化技术,只绘制当前实际可见的内容,而不是绘制所有 70,000 个项目。有一个来自 MSDN 的博客,其中包含 VirtualCanvas 类的源代码,如果您已经在使用 Canvas,它几乎可以适应任何用途。

请注意 - 虚拟化有一些特定条件可以虚拟化项目。

http://blogs.msdn.com/b/jgoldb/archive/2008/03/08/performant-virtualized-wpf-canvas.aspx

【讨论】:

    猜你喜欢
    • 2021-07-21
    • 2020-12-12
    • 1970-01-01
    • 2019-05-03
    • 2020-07-21
    • 1970-01-01
    • 1970-01-01
    • 2013-09-27
    • 1970-01-01
    相关资源
    最近更新 更多