C# - Creating Custom Shapes In WPF [Beginner]


I don't know if you've ever had to draw your own shapes before WPF, but if not, I can tell you it used to be a lot harder. You don't have to override the OnPaint function or listen for the Paint event anymore. You simply create a shape as if it were any other object and position it where you want it. WPF takes care of everything else.

In order to play around with XAML and WPF, you should probably pick up a copy of either Visual Studio 2008 or Expression Blend 2. You can download any of the free Express Editions of Visual Studio right here.

Let's start out with an easy example to get warmed up. One of my favorite games is Tetris, so here's a Tetris shape.

Simple Tetris Shape

Here's all the code required to make the above application.
<Window x:Class="XAMLCustomShapes.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="XAML Custom Shapes" Height="350" Width="350">
  <Canvas>
    <Path Canvas.Left="15" Canvas.Top="50" Stroke="Black"
        Data="M 0,0 L 200,0 L 200,100 L 300,100 L 300,200 L 100,200 L 100,100 L 0,100 Z">
      <Path.Fill>
        <LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
          <GradientStop Offset="0" Color="DarkBlue" />
          <GradientStop Offset="1" Color="LightBlue" />
        </LinearGradientBrush>
      </Path.Fill>
    </Path>
  </Canvas>
</Window>
 
There's a lot of stuff here, but the only thing we care about is the Path object and its Data property. A Path has a lot of power when it comes to building custom shapes and that's what I'll be using today to draw them.

The shape is defined by the text in the Data property. The syntax might look a little weird, but once you get used to it you'll find it to be a lot more efficient than previous methods. If you've drawn shapes with other languages, you might be familiar with the functions named something like lineTo, moveTo, arcTo, etc. WPF replaced those calls with one long string that contains special characters to represent each function. In my example, "L" refers to lineTo and "M" refers to moveTo. Below is an image that shows what each piece of the string represent what piece of the shape.

Annotated Simple Tetris Shape
As you can see, I started my shape with an "M 0,0". This places my pen at the top-left corner of the shape. You can start the shape wherever you like, but (0,0) worked well for my Tetris block. The next piece, "L 200,0", draws a line from my starting point, (0,0), to the point specified after the "L", (200,0). When you draw a line the pen moves to the new point, so any subsequent calls will start from the last position. I simply continued using the "L" command to draw lines until my shape was complete. The last character in my data string, "Z", tells the path that the shape is finished.

There are a lot more commands other than "M", "L", and "Z". I'm not going to describe all of them since MSDN has a nice article on PathMarkup Syntax that describes them in detail.

All right, I think we've got straight lines under control. Let's look a something a little more complicated - curves. WPF has lots of different types of curves - Quadratic Bezier, Smooth cubic Bezier, Smooth quadratic Bezier, and Elliptical Arc. Yep, I don't know what they are either, but the msdn article I mentioned in the last paragraph does an ok job of explaining them. If you really, really want to know more, check out the Wikipedia article on BezierCurves.

Tetris Shape Curved

Here's the same Tetris piece with some curves on the top and bottom. And the code:
<Window x:Class="XAMLCustomShapes.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="XAML Custom Shapes" Height="350" Width="350">
  <Canvas>
    <Path Canvas.Left="15" Canvas.Top="50" Stroke="Black"
        Data="M 0,0 A 15,5 180 1 1 200,0 L 200,100 L 300,100 
              L 300,200 A 15,5 180 1 1 100,200 L 100,100 L 0,100 Z">
      <Path.Fill>
        <LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
          <GradientStop Offset="0" Color="DarkBlue" />
          <GradientStop Offset="1" Color="LightBlue" />
        </LinearGradientBrush>
      </Path.Fill>
    </Path>
  </Canvas>
</Window>
 
For the curves, I used an EllipticalArc. Basically, all an Elliptical Arc does is draw a curve between the current point and the specified endpoint. The options for the arc are (in order in which they appear in the string): ArcSize (15, 5), RotationAngle (180), IsLargeArc (1), SweepDirection (1), and the endpoint. The MSDN article on Elliptical Arc explains each one of those properties, so I won't repeat them here. As you can see, drawing curves is not that much different than drawing lines - there's just a few more options.

I think that just about does it for custom shapes. Using straight lines and the Elliptical Arc, you can make just about any imaginable shape. Of course, there are lots of different path options you should definitely explore to get your shapes exactly how you want them. Happy drawing.

Comments