Click here to Skip to main content
15,867,330 members
Articles / Desktop Programming / WPF
Article

WPF and .NET 3.5 - Drawing Customized Controls and Custom UI Elements

Rate me:
Please Sign up or sign in to vote.
4.80/5 (24 votes)
10 Dec 2007CPOL8 min read 161.8K   3.9K   106   19
Using Visual Studio 2008 for custom drawing using WPF and .NET 3.5; fun with Spirographs
Screenshot - windowsmall.jpg

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:

XML
<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>

Screenshot - tile.jpg

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:

Screenshot - layout.jpg

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:

XML
<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>

Screenshot - box.jpg

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:

XML
<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.

XML
<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>

Screenshot - button.jpg

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.

C#
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.

XML
<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:

C#
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 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.

C#
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!

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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer PageLabs
United States United States
I'm the founder of PageLabs, a web-based performance and SEO optimization site.

Give your site a boost in performance, even take a free speed test!

http://www.pagelabs.com

Comments and Discussions

 
GeneralMy vote of 5 Pin
gaesquivel3-Jan-11 9:58
gaesquivel3-Jan-11 9:58 
Generalangled Lines Pin
hari krishna21022-Mar-10 19:29
hari krishna21022-Mar-10 19:29 
GeneralRe: angled Lines Pin
TylerBrinks23-Mar-10 5:36
TylerBrinks23-Mar-10 5:36 
Generalangled lines Pin
hari krishna21020-Feb-10 0:20
hari krishna21020-Feb-10 0:20 
GeneralRe: angled lines Pin
TylerBrinks20-Feb-10 3:43
TylerBrinks20-Feb-10 3:43 
GeneralMemory Leak with Background Texture Pin
Ed Hannigan3-Jan-09 6:25
Ed Hannigan3-Jan-09 6:25 
GeneralRe: Memory Leak with Background Texture Pin
TylerBrinks4-Jan-09 0:52
TylerBrinks4-Jan-09 0:52 
GeneralExcelent article Pin
Nedim Sabic15-Nov-08 11:14
Nedim Sabic15-Nov-08 11:14 
GeneralRe: Excelent article Pin
TylerBrinks15-Nov-08 11:59
TylerBrinks15-Nov-08 11:59 
GeneralPerformance for large Outer Radius Pin
John Simmons19-Feb-08 6:29
John Simmons19-Feb-08 6:29 
Hello,

I know the point of the article was to show capability of WPF to do nice things easily, but it does show a general problem with with WPF at the moment in that performance quickly becomes very bad for complex geometries. For this example, poor performance for large outer radius, when moving application about and resizing, could be fixed I think, by using DrawingVisual, instead of overriding OnRender. The problem is that the OnRender is useful, when need more control on what is rendered. For example, I am trying to write an application where the pen thickness must stay constant, when the rendered graphic is resized, so that I cannot use a ViewBox as would be standard.
GeneralRe: Performance for large Outer Radius Pin
TylerBrinks19-Feb-08 11:34
TylerBrinks19-Feb-08 11:34 
NewsRe: Performance for large Outer Radius Pin
John Simmons19-Feb-08 22:27
John Simmons19-Feb-08 22:27 
GeneralAwesome!!! Pin
venkatrajagopal11-Dec-07 6:20
venkatrajagopal11-Dec-07 6:20 
GeneralRe: Awesome!!! Pin
TylerBrinks13-Dec-07 8:54
TylerBrinks13-Dec-07 8:54 
GeneralI love it! Pin
SimPlayer11-Dec-07 3:07
SimPlayer11-Dec-07 3:07 
GeneralRe: I love it! Pin
TylerBrinks13-Dec-07 8:51
TylerBrinks13-Dec-07 8:51 
QuestionScreen Saver? Pin
JoeIsTheMan10-Dec-07 8:09
JoeIsTheMan10-Dec-07 8:09 
AnswerRe: Screen Saver? Pin
TylerBrinks13-Dec-07 8:50
TylerBrinks13-Dec-07 8:50 
GeneralRe: Screen Saver? Pin
JoeIsTheMan2-Jan-08 18:16
JoeIsTheMan2-Jan-08 18:16 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.