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.
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.
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;
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!