Now for the fun part. This is where we get to build a custom control for drawing the Spirograph. As it turns out, the Canvas element has all the drawing methods and context needed to draw or animate a Spirograph. Let’s take a look at how a Canvas object can be extended for custom drawing.

Collapse

public class GraphContext : Canvas
{
    protected override void OnRender(DrawingContext drawingContext){…}
    protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo){…}
}

Nothing fancy here except that overriding the OnRender method gives us access to the DrawingContext object. You can liken this to theGraphics object in previous versions of .NET. We'll use the DrawingContext to add custom shapes to the canvas for custom rendering and animation.

I won't belabor how to extend a control; that’s just standard OOP practices. Instead, let’s look at how we can get this custom control on to our window. Once again, it’s just a little bit of XAML. This time, however, we need to add a little definition about the control using standard XML namespacing.

Collapse

<window xmlns:codeprojectexample="clr-namespace:WpfApplication1"
    x:class="WpfApplication1.GraphWindow">

<graphcontext background="Black" x:name="graphSurface"></graphcontext>

This xmlns declaration tells WPF to include the WpfApplication1 namespace using a CodeProjectExample prefix. This is a bit like the section of an ASP.NET page if you're not as familiar with XML.

The XAML to reference the custom element couldn't be easier. It is a simple combination of the prefix we defined and the class name of the element. Any additional properties can be defined inline. In this case, I'm setting the drawing area to black.

Shapes

Drawing shapes in WPF isn't necessarily easier than in previous versions of .NET, but there are some nice amenities in the WPF Framework, namelyShape UI elements. Since an epicycloid (like any mathematical graph) is essentially a series of connected X and Y points, we can draw a collection of lines to visually represent the graph. Here’s how the line segments are generated:

Collapse

private void DrawStaticGraph(DrawingContext drawingContext)
{
    // PathGeometry is a nice alternative to drawingContext.DrawLine(...) as it
    // allows the points to be rendered as an image that can be further manipulated
    PathGeometry geometry = new PathGeometry();

    // Add all points to the geometry
    foreach (Points pointXY in _points)
    {
        PathFigure figure = new PathFigure();
        figure.StartPoint = pointXY.FromPoint;
        figure.Segments.Add(new LineSegment(pointXY.ToPoint, true));
        geometry.Figures.Add(figure);
    }

    // Add the first point to close the gap from the graph's end point
    // to graph's start point
    PathFigure lastFigure = new PathFigure();
    lastFigure.StartPoint = _points[_points.Count - 1].FromPoint;
    lastFigure.Segments.Add(new LineSegment(_firstPoint, true));
    geometry.Figures.Add(lastFigure);

    // Create a new drawing and drawing group in order to apply
    // a custom drawing effect
    GeometryDrawing drawing = new GeometryDrawing(this.Pen.Brush, this.Pen, geometry);
    DrawingGroup drawingGroup = new DrawingGroup();
    drawingGroup.Children.Add(drawing);

    ...
}

Let me take a minute to explain that there are a couple ways of drawing lines (specifically the DrawLine method on the DrawingContext object), but there’s a purpose to this code. We'll get to that point shortly. For now, let’s take note of a couple important points. Notice that we have aPathGeometry object to which we're adding a collection of PathFigure objects. Finally, the geometry object is added to a DrawingGroup. Read between the lines and you'll see that WPF is capable of rendering any number of shapes or figures in one fell swoop. I find that impressive.

Effects

Like I said, there’s a point to the verbose code above. I didn't just want to draw an epicycloid, I wanted to draw it and make it look really smooth. That’s where WPF REALLY starts to shine. I don't even want to begin to imagine how to add a blur effect to individual line segments in .NET 2.0. It would take plenty of custom code. The theme of this article is how simply WPF can achieve stunning visual effects. Here’s the three lines of code needed to soften the entire Spirograph.

Collapse

BlurBitmapEffect blurEffect = new BlurBitmapEffect();
blurEffect.Radius = Softness;
drawingGroup.BitmapEffect = blurEffect;

Screenshot - comparison.jpg

I wish I had more code to show for this snippet, but that’s it. Splendid, if you ask me! There are dozens of effects in the WPF Framework, all of which are customizable both in XAML and in code. In most cases, it only takes a few lines of code like this to apply an effect to part or all of an image. All that’s left to render the graph is to call the Add() method to add the drawing to our custom canvas. That's it!

Last edited Sep 1, 2010 at 4:42 PM by astellar, version 3

Comments

No comments yet.