Skip to main content

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

Popular posts from this blog

C# Snippet - Shuffling a Dictionary [Beginner]

Randomizing something can be a daunting task, especially with all the algorithms out there. However, sometimes you just need to shuffle things up, in a simple, yet effective manner. Today we are going to take a quick look at an easy and simple way to randomize a dictionary, which is most likely something that you may be using in a complex application. The tricky thing about ordering dictionaries is that...well they are not ordered to begin with. Typically they are a chaotic collection of key/value pairs. There is no first element or last element, just elements. This is why it is a little tricky to randomize them. Before we get started, we need to build a quick dictionary. For this tutorial, we will be doing an extremely simple string/int dictionary, but rest assured the steps we take can be used for any kind of dictionary you can come up with, no matter what object types you use. Dictionary < String , int > origin = new Dictionary < string , int >();

C# Snippet - The Many Uses Of The Using Keyword [Beginner]

What is the first thing that pops into your mind when you think of the using keyword for C#? Probably those lines that always appear at the top of C# code files - the lines that import types from other namespaces into your code. But while that is the most common use of the using keyword, it is not the only one. Today we are going to take a look at the different uses of the using keyword and what they are useful for. The Using Directive There are two main categories of use for the using keyword - as a "Using Directive" and as a "Using Statement". The lines at the top of a C# file are directives, but that is not the only place they can go. They can also go inside of a namespace block, but they have to be before any other elements declared in the namespace (i.e., you can't add a using statement after a class declaration). Namespace Importing This is by far the most common use of the keyword - it is rare that you see a C# file that does not h

C# WPF Printing Part 2 - Pagination [Intermediate]

About two weeks ago, we had a tutorial here at SOTC on the basics of printing in WPF . It covered the standard stuff, like popping the print dialog, and what you needed to do to print visuals (both created in XAML and on the fly). But really, that's barely scratching the surface - any decent printing system in pretty much any application needs to be able to do a lot more than that. So today, we are going to take one more baby step forward into the world of printing - we are going to take a look at pagination. The main class that we will need to do pagination is the DocumentPaginator . I mentioned this class very briefly in the previous tutorial, but only in the context of the printing methods on PrintDialog , PrintVisual (which we focused on last time) and PrintDocument (which we will be focusing on today). This PrintDocument function takes a DocumentPaginator to print - and this is why we need to create one. Unfortunately, making a DocumentPaginator is not as easy as