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

C# WPF Tutorial - Implementing IScrollInfo [Advanced]

The ScrollViewer in WPF is pretty handy (and quite flexible) - especially when compared to what you had to work with in WinForms ( ScrollableControl ). 98% of the time, I can make the ScrollViewer do what I need it to for the given situation. Those other 2 percent, though, can get kind of hairy. Fortunately, WPF provides the IScrollInfo interface - which is what we will be talking about today. So what is IScrollInfo ? Well, it is a way to take over the logic behind scrolling, while still maintaining the look and feel of the standard ScrollViewer . Now, first off, why in the world would we want to do that? To answer that question, I'm going to take a an example from a tutorial that is over a year old now - Creating a Custom Panel Control . In that tutorial, we created our own custom WPF panel (that animated!). One of the issues with that panel though (and the WPF WrapPanel in general) is that you have to disable the horizontal scrollbar if you put the panel in a ScrollV...