Lightweight Drawing with DrawingVisuals: Part 2 of 2

I apologize for writing this entry almost a month after the first part of this series. I was busy making the Sun Launcher draggable in the Windows version of Jing (which was pushed yesterday). Yay!

Anyhow, in this entry I will teach you how to do lightweight drawing with DrawingVisuals. Most visuals in WPF derive from FrameworkElement, which contains overhead that affects rendering performance that we can avoid with DrawingVisuals.

However, you can’t simply use DrawingVisuals in XAML. DrawingVisuals don’t contain support for layout, so the runtime won’t be able to position them and ultimately have them rendered. To use DrawingVisuals, you have to host them in a class that does provide layout, such as a FrameworkElement.

Ok, so let’s learn how to host DrawingVisuals! To help distill the concepts for you, we’ll just draw a black square.

Step 1: Create a class that will host your visual(s)

step-1.png

SquareVisual is our host, and will have support for layout because it derives from FrameworkElement. _square will be the square that we will draw.

Step 2: Register your visual(s) with the host

step-2.png
Note how there are calls to AddVisualChild() and AddLogicalChild(). These calls are required, or the visual that you are hosting will not be rendered. This is because they tell the host (SquareVisual) of the existence of _square, which will force it to participate in its layout.

Step 3: Maintain bookkeeping of our visuals

step-3.png

We need to implement these two overrides so the rendering system can maintain proper bookkeeping. Since we only have 1 visual, _square, implementing them is trivial. However, if you have multiple visuals to maintain, it can get ugly. Luckily, Microsoft provides a class called VisualCollection which you could use in place of _square. Then implementing these two overrides becomes simple again.

Step 4: Draw the visual

In Step 2, there was this function:

step-4.png

This function is what draws the square. To render a drawing visual, we describe its properties by calling RenderOpen() to get access to an instance of DrawingContext, which provides functions for drawing various shapes and geometries. It’s somewhat reminiscent of a DeviceContext. If you’ve done custom drawing with GDI, you may be wondering why we only called DrawRectangle once, and why aren’t we drawing inside OnRender()? Those are very good questions, and we need to understand how rendering works in WPF.

WPF rendering is entirely different from Win32. In Win32/MFC apps, the app was responsible for updating the visual state of a window every time a part of the client area was invalidated. If you’ve done custom drawing with GDI, you know how time consuming it could be to write complex drawing routines to make sure visuals got updated properly on every call to OnRender(), prevent flickering and tearing, etc. WPF however, utilizes what is called a Retained Mode system. In WPF apps, the runtime is responsible for maintaining the visual state of elements in a window.

The implications of this are profound. What this means is anytime you draw a visual (ie DrawRectangle()), what you are really doing is describing the properties of your visual to the rendering system with rendering instructions that are serialized for later use in the graphics pipeline. This is beneficial because the rendering system can optimize rendering performance by minimizing the number of redraws that are required, prevent flickering, etc. Best of all, any changes to your visuals are converted to serialized rendering instructions that the rendering system is responsible for maintaining.

All that’s left to do is to add SquareVisual in your window’s XAML file and you now have a custom visual.

Now you know how to do lightweight drawing with DrawingVisuals. While this technique is not hard, the performance you gain comes at the cost of flexibility with describing your visuals in XAML, losing the ability to do binding on your visuals, creating dependency properties etc.

If you only have a few visuals, say < 10, go ahead and use the visuals that WPF already provides as it’s not worth spending the time to host custom visuals.

The complete source code is below. It was made with Visual Studio 2005 and the WPF extensions installed.

DrawingVisualSample.zip

Explore posts in the same categories: WPF

Tags: , , , ,

You can comment below, or link to this permanent URL from your own site.

2 Comments on “Lightweight Drawing with DrawingVisuals: Part 2 of 2”

  1. robfiregarden Says:

    Is it possible to animate properties of this square? Such that we can make it move around the screen?

    Rob

    http://www.robsword.net

  2. Jerry Lin Says:

    Hi Rob,

    Yes, it is possible to animate the square. Remember that DrawingVisuals are lightweight, so they won’t necessarily have the properties that you want to animate. In this case, the square doesn’t have Top and Left properties to move the square.

    One workaround is to calculate where you want the square to move wrt to its host, and move it incrementally every time CompositionTarget.Rendering is called. I would avoid this though because it is called for every single frame that is about to be rendered and can be CPU intensive.

    Another way off the top of my head would be to host the square inside the Canvas panel. This way you can take advantage of the Canvas.Left and Canvas.Top attached properties and animate them for the square.

    Of if you like to reinvent the wheel, you can declare similar attached properties inside the FrameworkElement that is hosting your visuals. But be sure to declare the properties with the AffectsParentArrange and AffectsRender metadata flags. Then implement ArrangeOverride so that the square’s top-left is placed wrt to the values of those properties. Now you can animate the square’s position inside XAML using Storyboards. This is similar to how the Sun launcher is animated in Jing when the cursor is on top of it. (but the balls are restyled buttons instead of being DrawingVisuals).

    Sorry for the lengthy response. This should be another post that I explore in the future.

    Jerry

Comment:

You must be logged in to post a comment.