Как улучшить производительность рендеринга Canvas?

Мне нужно сделать много Shape (около 1/2 сотни тысяч) в качестве [Canvas] [2] детей. Я делаю это в своем приложении WPF, разделяя работу на две части: во-первых, я создаю фигуры, задавая свойства каждого из них (например, Margin, Fill, Width и т.д.) После добавления фигур в качестве Canvas children.

MyCanvas.Children.Add(MyShape)

Теперь я хочу улучшить производительность второй части, потому что когда я рисую фигуры, мое приложение блокируется в течение длительного периода времени. Поэтому я попытался использовать Dispatcher и его метод [BeginInvoke] [4] с различными [приоритетами] [5]: только если я использую приоритет фона, основной приложение не блокируется, в противном случае приложение остается заблокированным, а "изображение" не отображается до тех пор, пока все фигуры не будут добавлены в мой холст, но если я использую приоритет "Фоновый", очевидно, все будет медленнее. Я также попытался создать новый поток вместо использования диспетчера, но существенных изменений не было.

Как я могу исправить эту проблему и вообще улучшить производительность моего приложения, когда добавляю свои фигуры в Canvas?

Спасибо.

Ответ 1

Вам нужно использовать Visual вместо Shape; в частности, как было предложено, DrawingVisual: визуальный объект, который можно использовать для визуализации векторной графики. Фактически, как написано в библиотеке MSDN:

DrawingVisual - это легкий класс рисования, который используется для визуализации фигур, изображений или текста. Этот класс считается легким, потому что он не обеспечивает компоновку, ввод, фокусировку или обработку событий, что повышает его производительность. По этой причине рисунки идеально подходят для фона и клипа.

Так, например, для создания DrawingVisual, который содержит прямоугольник:

private DrawingVisual CreateDrawingVisualRectangle()
{
   DrawingVisual drawingVisual = new DrawingVisual();

   // Retrieve the DrawingContext in order to create new drawing content.
   DrawingContext drawingContext = drawingVisual.RenderOpen();

   // Create a rectangle and draw it in the DrawingContext.
   Rect rect = new Rect(new System.Windows.Point(160, 100), new System.Windows.Size(320, 80));
   drawingContext.DrawRectangle(System.Windows.Media.Brushes.LightBlue, (System.Windows.Media.Pen)null, rect);

   // Persist the drawing content.
   drawingContext.Close();

   return drawingVisual;
}

Чтобы использовать объекты DrawingVisual, вам необходимо создать контейнер-хост для объектов. Объект контейнера-хоста должен быть получен из класса FrameworkElement, который обеспечивает поддержку макета и обработки событий, которой не обладает класс DrawingVisual. Когда вы создаете объект контейнера-хоста для визуальных объектов, вам нужно сохранить ссылки на визуальные объекты в VisualCollection.

public class MyVisualHost : FrameworkElement
{
   // Create a collection of child visual objects.
   private VisualCollection _children;

   public MyVisualHost()
   {
       _children = new VisualCollection(this);
       _children.Add(CreateDrawingVisualRectangle());

       // Add the event handler for MouseLeftButtonUp.
       this.MouseLeftButtonUp += new System.Windows.Input.MouseButtonEventHandler(MyVisualHost_MouseLeftButtonUp);
   }
}

Процедура обработки событий затем может реализовать тестирование на удар, вызывая метод HitTest. Параметр метода HitTestResultCallback относится к пользовательской процедуре, которую вы можете использовать для определения результата действия теста.

Ответ 2

Согласился, что если вы хотите нарисовать миллионы элементов, вы просто не сможете сделать это в WPF. WriteableBitmapEx, как упоминалось, является хорошей альтернативой.

См. этот связанный вопрос, который углубляется в высокоэффективную графику в WPF и доступных альтернативах.

Если вы просто должны использовать Canvas, посмотрите ZoomableApplication2 - миллион элементов. Это демонстрация на основе Canvas, которая сильно использует виртуализацию для получения разумной производительности с 1,000,000 UIElements на холсте.

Ответ 3

Это много UIElements и, вероятно, не даст такой производительности, которую вы ищете. Вам нужно иметь возможность взаимодействовать с каждым из элементов, которые вы визуализируете? Если нет, я бы настоятельно рекомендовал вместо этого использовать WriteableBitmap. Если вам нужно нарисовать фигуры и не хотите самостоятельно создавать всю эту логику (кто захочет?), проверить проект WriteableBitmapEx на CodePlex.

Ответ 4

Это может быть несколько несвязанным, и я извиняюсь, если вы так чувствуете, но в надежде, что он может пролить свет другим пользователям, я поделюсь этим лакомым кусочком.

У нас были некоторые проблемы с производительностью с элементом управления Canvas, используемым для захвата подписей. Захват был очень зазубрен, и в результате мы не могли рисовать кривые линии. Оказалось, что это связано с тем, что стиль создавал тени для элементов пользовательского интерфейса. Отключение эффекта drop-shadow решило нашу проблему.