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)

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

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

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:

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.
Tags: drawing, DrawingVisual, performance, rendering, WPF
You can comment below, or link to this permanent URL from your own site.
February 10, 2008 at 3:08 am
Is it possible to animate properties of this square? Such that we can make it move around the screen?
Rob
http://www.robsword.net
February 11, 2008 at 1:53 pm
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
September 24, 2008 at 6:28 am
Hi Jerry,
I ran into some problems after implementing the code above and i’ll be glad if you could give me a hand. My problem is not directly connected to your implementation of the squarevisual class.
See i need a lightweight class to pass that draws one visual and passes datacontext to it. I’ve implemented a pseudo contentcontrol following your directions above and inheriting directly from frameworkelement, because i strictly need my control not to to have a template. Here is my implementation:
class Testpanel : FrameworkElement
{
public Visual Content
{
get { return (Visual) GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
// Using a DependencyProperty as the backing store for Content. This enables animation, styling, binding, etc…
public static readonly DependencyProperty ContentProperty =
DependencyProperty.Register(“Content”, typeof(Visual), typeof(Testpanel), new FrameworkPropertyMetadata(null, ContentChanged));
private static void ContentChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
(sender as Testpanel).HandleContentChanged(e.NewValue, e.OldValue);
}
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
}
private void HandleContentChanged(object p, object p_2)
{
this.AddLogicalChild(p);
this.AddVisualChild(p as Visual);
}
protected override Visual GetVisualChild(int index)
{
return this.Content != null ? this.Content : null;
}
protected override int VisualChildrenCount
{
get
{
return this.Content != null ? 1 : 0;
}
}
protected override Size ArrangeOverride(Size finalSize)
{
(this.Content as FrameworkElement).Arrange(new Rect(new Point(0, 0), finalSize));
return finalSize;
}
protected override Size MeasureOverride(Size availableSize)
{
(this.Content as FrameworkElement).Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
return availableSize;
}
}
So far so good! i call it from xaml as so:
Now without the Loaded event handler it works as expected the button’s content is “TestData”. now in the Testpanel_Loaded event handler i try and change the testpanel’s DataContext and nothing changes as it would if Testpanel inherited from ContentControl or even Content for that matter. I’ve added a DataContext propertycallback in i custom button that i’ve added as content and what i discovered was that the DataContext changes but somehow the button’s binding does not happen. Do you have any ideas why this is so or what is it that the Control adds up to the FrameworkElement that it makes the binding magic happen?
thanx in advance
jim
September 24, 2008 at 7:14 am
sorry for the last post i guess i just cannot add xaml code directly anyhow what i have is this:
Grid DataContext=”TestData”
Testpanel Loaded=”Testpanel_Loaded”
Testpanel.Content
Button Content=”{Binding}”