How to improve Canvas rendering performance? - wpf

I have to draw a lot of Shape (about 1/2 hundred thousand) as [Canvas][2]'s childrens. I make this in my WPF application dividing work in two parts: first thing I create shapes by setting the properties of each of them (like Margin, Fill, Width, etc...), after I add shapes as Canvas's children.
MyCanvas.Children.Add(MyShape)
Now i want to improve the performance of the second part, because when i draw the shapes my application is blocked for a long period of time. So i tried to use the Dispatcher and its method [BeginInvoke][4] with different [priorities][5]: only if I use the Background priority the main application does not block, otherwise the application remains blocked and the "picture" is not displayed until all shapes are added to my Canvas, but if I use the Background priority obviously everything is slower. I also tried to create a new thread instead of using the Dispatcher, but there was no significant change.
How can I fix this problem, and generally improve the performance of my application when I add my shapes to Canvas?
Thanks.

Need to use Visual objects instead of Shape; in particular, as suggested, DrawingVisual: a visual object that can be used to render vector graphics. In fact, as written in the MSDN library:
DrawingVisual is a lightweight drawing class that is used to render shapes, images, or text. This class is considered lightweight because it does not provide layout, input, focus, or event handling, which improves its performance. For this reason, drawings are ideal for backgrounds and clip art.
So, for example, to create a DrawingVisual that contains a rectangle:
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;
}
In order to use DrawingVisual objects, you need to create a host container for the objects. The host container object must derive from the FrameworkElement class, which provides the layout and event handling support that the DrawingVisual class lacks. When you create a host container object for visual objects, you need to store the visual object references in a 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);
}
}
The event handling routine can then implement hit testing by invoking the HitTest method. The method's HitTestResultCallback parameter refers to a user-defined procedure that you can use to determine the resulting action of a hit test.

Agreed that if you want to draw millions of elements, you simply can't do it in WPF. WriteableBitmapEx as mentioned is a good alternative.
See this related question which goes into depth on high performance graphics in WPF and the alternatives available.
If you simply must use Canvas, check out this ZoomableApplication2 - A million items. This is a Canvas based demo which makes heavy use of Virtualization to get reasonable performance with 1,000,000 UIElements on a Canvas.

That's a lot of UIElements and probably isn't going to give the kind of performance you're looking for. Do you need to be able to interact with each of the elements you're rendering? If not, I would highly recommend looking into using WriteableBitmap instead. If you need to draw shapes and don't want to create all that logic yourself (who would want to?), check out the WriteableBitmapEx project over on CodePlex.

This may be somewhat unrelated, and I apologize if you feel this way, but in the hopes that it can shed some light for other users, I'll share this tidbit.
We had some performance issues with a Canvas control used for capturing signatures. The capture was very jagged, and we couldn't draw curved lines as a result. It turned out to be related to a style was was generating drop-shadows on the UI elements. Disabling the drop-shadow effect solved our problem.

Related

Speed of Thousands of Rectangles (with a Stroke!!!) in a WPF Canvas

this is my fist question here,
but I didn't find a real answer to my question.
I work on a visualization program and use C# and WPF.
I need to draw a treemap. I actually create Rectangle objects and then add them to the Canvas which works really nice and is very useful, since I also add event handlers to those rectangles (mouse click).
I have a problem with the performance though.
When I add the rectangles and set the Stroke property (SolidColorBrush) the whole process is slowing down extremely. Without those strokes set the speed is ok.
I already improved the performance a little by adding Rectangle objects to a newly created Canvas object and then adding this new Canvas object to the original Canvas object (so not all rectangles are direkt children of the original Canvas, which should help speeding things up).
So my question is how it is possible to add a Stroke to all those Rectangles without breaking the speed.
You can find a comparison of the 'strokeless' and 'stroked' treemap versions in the links.
Treemap without Strokes
Treemap with Strokes
Thank you very much for your help!
edit:
Ok, I have found a solution!
Sorry for writing here first, but I already searched for hours to find a solution but now I found out tht the problem was my used SolidColorBrush!
I have something like this:
public static SolidColorBrush TreeMapBorderBrush;
And I use this Brush to set the Stroke property of every Rectangle that should be added to the Canvas. But exactly that was the problem.
The code looked like this before:
rect.Stroke = Visualization_Helper.TreeMapBorderBrush;
I changed it to this now:
rect.Stroke = Visualization_Helper.TreeMapBorderBrush.Clone();
So I only work with a copy of that Brush on each Rectangle now, which speeds up the processing like there was no Stroke (like in my question)!
Just for your information! Weird!

How to smooth WPF animation?

I am struggling in smoothing WPF animation
Actually my animation code is as follows:
private void AnimateX ( FrameworkElement element, double XMoveStart, double XMoveEnd, int secondX)
{
SineEase eEase = new SineEase();
eEase.EasingMode = EasingMode.EaseInOut;
Storyboard sb = new Storyboard();
DoubleAnimation daX = new DoubleAnimation(XMoveStart, XMoveEnd, new Duration(new TimeSpan(0, 0, 0, secondX, 0)));
daX.EasingFunction = eEase;
Storyboard.SetTargetProperty(daX, new PropertyPath("(Canvas.Left)"));
sb.Children.Add(daX);
element.BeginStoryboard(sb);
}
The above code is a method to move an object horizontally with sine ease. When only one object is moving, it is OK. However, whenever two or more objects move together (call AnimateX method on another object when the previous animation has not yet completed), the animation starts to become jittery. By jittery I mean, the objects are kind of shaking during the course of animation.
I faced the same problem many times. I found out that depending on the objects you add to your canvas, WPF will often have to regenerate representations of these objects on every frame (which I believe might be your case, depending on the type of UI elements you are manipulating). You can solve the jitter issue by telling WPF to cache a representation of your canvas in a bitmap. This is done very simply as follows, in your Xaml definition of the canvas:
<Canvas ...Your canvas properties...>
<Canvas.CacheMode>
<BitmapCache />
</Canvas.CacheMode>
...Your objects...
</Canvas>`
This reduces the load on your WPF application, as it simply stores the representation of your objects as a bitmap image, and as a consequence your application does not have to redraw them on every frame. This solution only works if your animation is applied externally to the canvas, and that there is no on-going local animations applying to the individual objects drawn in your canvas. You'll want to create separates canvases with their own caching if other animations in your code move the two objects with respect to each other.
Note that some UI elements will not be eased by this strategy. However, I've seen this strategy work efficiently for many elements, including TextBoxes and the likes, as well as geometric shapes. In any case, it's always worth the try.
Secondly, if caching local representations does not suffice, then you might want to have a look at the performance of your code and see if any process could be responsible for blocking the UI momentarily. There is no uniform solution regarding this aspect and it depends on what else is putting strain on your application UI. Cleaning the code and using asynchronous processes where relevant could help.
Finally, if, after all these checks the overall demand on your application remains too high, you can somewhat remove some strain on the application by reducing its general frame rate, the default being 60. You can try 30 or 40 and see if this improves the jittering by including the following code in your initialization:
Timeline.DesiredFrameRateProperty.OverrideMetadata(typeof(Timeline), new FrameworkPropertyMetadata { DefaultValue = 40 });
Just a guess, but what happens if you directly animate the property, withoud using a Storyboard?
private void AnimateX(FrameworkElement element, double xMoveStart, double xMoveEnd, double durationSeconds)
{
DoubleAnimation animation = new DoubleAnimation
{
From = xMoveStart,
To = xMoveEnd,
Duration = TimeSpan.FromSeconds(durationSeconds),
EasingFunction = new SineEase { EasingMode = EasingMode.EaseInOut }
};
element.BeginAnimation(Canvas.LeftProperty, animation);
}

Improving WPF Canvas performance

I am developing a Maps like application using WPF. I have ~10,000 PathGeometry, Shapes added to the canvas. I have added ScaleTransform and TranslateTransform for zooming and panning controls.
The problem I'm facing is, when I zoom or pan, there is slight lag. Is there a way to organize the data so that I handle only the Shapes that are visible?
Any hints on making it more efficient will be helpful and appreciated.
What kind f stuff are you putting on the canvas? If using pathGeometry, are you enclosing them in Path class? If so, Path has FrameworkElement in its superclass hierarchy, which is responsible for massive performance loss.
Take a look at my question here. Although it is about Shape class, but the reason of performance degradation is the same, FrameworkElement.
If you are doing so, the solution is to use PathGeometry instead, and enclose it in DrawingContext of a DrawingVisual using DrawingContext.DrawGeometry() method.
Here are some links that should help.
Path Geometry
DrawingContext.DrawGeometry()
Optimizing Performance: 2D Graphics and Imaging
And draw the shapes yourself, using combination of lines, and other things provided by classes derived from Geometry class (ArcGeometry, PathGeometry etc).
This should help.
If you want the ultimate in performance for immediate drawing in WPF, then check out WriteableBitmapEx. This is an excellent open source library, which I recently contributed to. it provides GDI-like drawing capabilities on WriteableBitmap and is compatible with Windows Phone, WPF and Silverlight. The API is simple, you get blitting, polygons, lines and simple shapes etc... You won't get datatemplates and gradient brushes however.

Rotating a .NET panel in Windows Forms

We use Windows Forms and custom user controls, and I would like to be able to rotate the panel hosting the userControl in a particular form. I have seen similar functionnalities with WPF, but I can't use it for the moment. Is it possible to achieve the rotation of a panel and its children using possibly built-in .NET methods or GDI+?
I have seen some pretty cool visual effect with menus that are displayed in game development, so I was wondering if it would be possible to create similar effects using Windows Forms.
Rotating a panel and its children in Windows Forms is not something directly supported, and I think it will end up being a buggy headache that could easily suck up lots of time. It's especially painful to think about when you could do this in WPF with zero lines of C# code and only a tiny bit of XAML.
You can use rotations in GDI+ by calling the RotateTransform method on a Graphics object.
However, rotating an entire control is not so simple, and will depend heavily on how the control is implemented.
If it's a composite UserControl that has other controls inside of it, you're out of luck.
If it's a sinlge control that paints itself, try inheriting the control, overriding the OnPaint method, and calling RotateTransform on the Graphics object. However, you will probably have trouble with it. In particular, you will probably need to override all of the mouse events and call the base control's events with rotated coordinates.
You can get halfway there by calling the DrawToBitmap method on your panel, then rotating the bitmap and displaying it e.g. in a PictureBox:
var bitmap = new Bitmap(panel.Width, panel.Height);
panel.DrawToBitmap(bitmap, new Rectangle(Point.Empty, panel.Size));
bitmap.RotateFlip(RotateFlipType.Rotate270FlipNone);
var pictureBox = new PictureBox();
pictureBox.Location = panel.Location;
pictureBox.SizeMode = PictureBoxSizeMode.AutoSize;
pictureBox.Image = bitmap;
Controls.Remove(panel);
Controls.Add(pictureBox);
Rotation angles other than 90-degree increments are also possible, if you draw the bitmap into another bitmap using GDI:
var bitmap2 = new Bitmap(bmp.Width + 75, bmp.Height + 100);
var graphics = Graphics.FromImage(bmp2);
graphics.TranslateTransform(bitmap2.Width / 2, bitmap2.Height / 2);
graphics.RotateTransform(-15f);
graphics.TranslateTransform(-bitmap.Width / 2, -bitmap.Height / 2);
graphics.DrawImageUnscaled(bitmap, Point.Empty);
graphics.Dispose();
The problem of course is that you're only displaying an image of your panel, and not the panel itself, so it's no longer possible to interact with the controls inside.
That could probably be done as well, but you would have to mess with window messages, which gets quite a bit more complicated. Depending on your needs you might also be able to get away with handling click and key events on the PictureBox, manipulating the controls in the panel, and then updating the image.

DrawingVisual performance with Opacity=0

If I have a DrawingVisual in WPF with Opacity=0, is that enough for it not to be drawn? We have hundreds of DrawingVisuals on a Canvas, and are currently setting Opacity=0 on the visuals that are not to be displayed, and I wanted to make sure there is no rendering performance hit for rendering a DrawingVisual with Opacity=0.
UPDATE: I have discovered through testing that there IS overhead when Opacity=0, but since DrawingVisual doesn't have a Visibility property, I don't know how else you would tell it to not be displayed unless you actualy remove it from the visual tree, so any suggestions are welcome.
I solved a very similar problem by using a DrawingGroup and adding or removing Drawing objects from the DrawingGroup as they either needed to be displayed or hidden. The key is to organize your Drawing objects in such a way that they are easy to manage and to understand how to add and remove them from the DrawingGroup.
Remember that you want to add and remove the Drawing objects from the DrawingCollection exposed by the DrawingGroup.Children property. So use DrawingGroup.Children.Add() or the other DrawingCollection methods: Insert, Remove, RemoveAt, Clear. You will need to keep an external list of the Drawing objects you add/remove to the DrawingGroup to do this successfully.
I used this technique to great effect by drawing an Image (bitmap) into the first child in my instance of DrawingGroup and then adding and removing Drawing objects to this instance of DrawingGroup in order to layer polygons, paths, text, etc on top of the drawing.
I "draw" or "erase" on the image by adding or removing Drawing objects to the instance of the DrawingGroup. The DrawingGroup is treated as a single Drawing and so any scaling, panning, or other manipulations will affect all Drawing objects within the DrawingGroup.
The best way to check would be to instead set the Visibility to Visibility.Colapsed, and see if there's any drawing performance differences.
Visibility.Colapsed ensures that the element is not visible but also that it will not participate in the Arrange, Measure and Render passes of the UI, while an element with Opacity=0 might participate in all passes.
The most efficient seems to be setting the opacity in my tests. Another simple approach is to redraw the visuals that are affected.
using (DrawingContext dc = RenderOpen()) {} //Hide this visual
And then redraw when they become visible again.
Rendering a blank drawingcontext seems to be very quick. But if you have complicated visuals it could take time to rerender them when they become visible.
Why not simply remove the visual from the visual children list? When it needs to be visible you add it back.

Resources