Contents
Introduction
Visual Studio 2008’s release has come and gone, and I can confidently say that I am fully overwhelmed. As of this writing, I've had almost no time to dive into new language features LINQ, let alone such major paradigm shifts like WPF. I'm approaching this article as a platform to learn WPF myself, but I hope you find it instructional as well. I decided to use the mathematical concept of an epicycloid as the subject for my first attempt at WPF. Most people will remember epicycloids from their youth as Spirographs. I had an endless fascination drawing Spirograph artwork as a kid, so it seems fitting to resurrect the memory for a graphics-related article.
What is WPF?
A Definition
For those not already familiar with what Windows Presentation Foundation is, WPF is Microsoft’s next generation platform for building Windows user interfaces. The beauty of WPF is that, for the first time, the presentation and layout that defines a Windows UI is decoupled from the code. The UI is rendered using XAML, the native WPF, XML-based mark up, while code remains in *.cs files.
Fundamental Changes
Most importantly, WPF should make most UI tasks exponentially easier. We'll explore that claim throughout this article.
The separation of XAML and code is essentially the same as the separation between HTML markup and an ASP.NET code behind file. This is a far cry from the visually limiting, drag and drop interface left over even from the VB 5 days. The clear benefit is that, with tools like Expressions, an application’s UI can be designed by a graphics team while a development team remains focused on development. This is especially important for someone like me considering some of the terrible UIs I've designed in my career.
A Custom Layout
Despite my lack of design talent, WPF should provide a hefty UI advantage. Let’s take a crack at a putting together a decent interface. The first stop along the path is adding a nice background. In years past, developers had to subscribe to or override a forms paint event, do some not-so-fun clipping calculations, and then manually paint their image to get what WPF offers in just a couple lines of XAML. Here’s the XAML for the tiled background:
<grid>
<grid.background>
<imagebrush viewport="0,0,0.3,0.3" viewbox="0,0,1,1" stretch="None"
tilemode="FlipXY" imagesource="background.jpg" />
</grid.background>
</grid>
Isn't it nice how the XAML almost speaks for itself? The main (parent) element on my form is a grid. I've added an ImageBrush
to the grid’s background. The image is set to tile by flipping every other tile horizontally (have fun coding that without WPF). The Viewbox
property simply represents the area of the original image to be displayed. The Viewbox
property is the target area where the Viewbox
will be displayed. In other words, the XAML above tells the WPF Framework which portion of the image should be tiled. Notice that as you change the Viewport
values in the XAML, the UI representation within Visual Studio 2008 dynamically updates!
Ok, the background was easy in just a couple lines of mark up. What we need now is a general window layout. I'm aiming for two columns with controls on the left and the Spirograph’s drawing surface on the right; something like this:
To pull this off, I'm using a combination of Grid
, StackPanel
, and DockPanel
objects. The dock panel is the primary container. That’s simple enough, to me, I want to dock the controls to the left and have the drawing surface fill the rest of the window. One hangup I found is that there’s no “Fill” property in WPF, where I've come to rely on that up through the 2.0 Framework. I did find, however, that the DockPanel
control has a handy property called LastChildFill
which does exactly what I need. Here’s the abbreviated XAML:
<dockpanel lastchildfill="True" margin="20,20,5,5 " name="dockPanel1" >
<stackpanel name="stackPanel1" width="200" dockpanel.dock="Left" >
...
</stackpanel>
<dockpanel lastchildfill="True" margin="20,0,15,15"
name="stackPanel2" dockpanel.dock="Right" >
...
</dockpanel>
</dockpanel>
Once again, terribly simple. We now have a left-docked panel and a filled right area.
Customizing Controls
I thought it would look nice for the controls to sit on top of a semi-transparent. Once again, without using WPF this would be a time consuming task. And once again, WPF makes short work of designing the UI. Take a look at the following XAML that renders a semi-transparent rectangle with rounded corners:
<rectangle width="200" opacity="0.5" stroke="Silver"
fill="White" radiusy="10" radiusx="10" height="260" />
That’s it. One line. It’s still sinking in how easily this is coming together. Note that in the source code, you'll see this Rectangle
object inside a Canvas
element. The quick explanation is that the Canvas
element allows for controls to overlap one another.
Next I thought I'd try customizing a standard button. I wanted a button with a PNG image on the left edge. This is one task that isn't difficult in previous versions of .NET and Visual Studio, so let’s put the XAML to the test and see if it’s either easier or more customizable in WPF. Here’s the XAML for an image button.
<button name="drawButton" margin="10,10,0,10" width="80" height="23"
horizontalalignment="Right">
<stackpanel width="Auto" height="Auto" horizontalalignment="Left"
orientation="Horizontal">
<img height="16" width="16" stretch="Fill" source="draw.png" />
<textblock margin="10,0,50,0" text="Draw" fontsize="12"
verticalalignment="Center" />
</stackpanel>
</button>
Did you notice that the button is acting as a host for child controls? The implications are endless! Unlike a standard WinForms button, this WPF button is as feature rich as any other drawn element in the WPF Framework. Ever wanted a button with an integrated drop down list? Me neither, but it’s possible. Hopefully by now you're seeing how much power is in so little markup.
Custom Drawing
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.
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 the Graphics
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.
<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, namely Shape
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:
private void DrawStaticGraph(DrawingContext drawingContext)
{
PathGeometry geometry = new PathGeometry();
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);
}
PathFigure lastFigure = new PathFigure();
lastFigure.StartPoint = _points[_points.Count - 1].FromPoint;
lastFigure.Segments.Add(new LineSegment(_firstPoint, true));
geometry.Figures.Add(lastFigure);
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 a PathGeometry
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.
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!
A Note on Animation
WPF has a bunch of build in features to support animation. Unfortunately, I haven't found any yet that can paint a series of lines in succession. Until I find a way to do that, the animation in this sample is facilitated by a standard timer. I will say, however, that the ability to add and remove rendering elements from the canvas at run time makes animating the Spirograph extremely simple, and there are less overall pieces that have to be buffered and redrawn. I didn't find the animation solution I was looking for, but I'm still impressed!
Conclusion
I really love how simple a custom UI can be with WPF. It’s a very powerful framework, and we've just scratched the surface.