Click here to Skip to main content
13,456,901 members
Click here to Skip to main content
Add your own
alternative version


34 bookmarked
Posted 22 Jan 2008

WPF Arrow and Custom Shapes

, 26 Jan 2008
Rate this:
Please Sign up or sign in to vote.
This article demonstrates how to create an Arrow shape in WPF, and provides guidelines for creating custom shapes


WPF is the best UI Framework ever. It provides us with a large arsenal of vector graphic types such as Line, Ellipse, Path and others. Sometimes we need shapes which are not provided in the WPF arsenal (such an Arrow), and with all due respect to the Path shape, which can be used to create any type of 2D shape, we do not want to recalculate each point every time. This is a good reason and opportunity to create a custom shape.


WPF provides two kinds of vector types: Shapes and Geometries.

Shape is any type that derives from the Shape base class. It provides Fill, Stroke and other properties for coloring, and is actually a FrameworkElement. Thus we can place shapes inside Panels, we can register shapes routed events and do anything related to FrameworkElement. (MSDN)

Geometry is any type that derives from the Geometry base type. It provides properties for describing any type of 2D geometry. A geometry is actually a Freezable type, thus can be frozen. A frozen object provides better performance by not notifying changes, and can be safely accessed by other threads. Geometry is not Visual, hence should be painted by other types such as Path. (MSDN)

Using the Code

Now that we have a little background, and we know what the differences between a Geometry and Shape are, we can create our shape based on one of these two types. Correct?

Well, surprisingly we can't base our custom shape on the Geometry type, since its one and only default constructor is marked as internal. Shame on you Microsoft.

Don't worry! We still have an option to base our custom shape on the Shape base class.

Now, let’s say that we want to create an Arrow shape. An arrow is actually a kind of line, so let’s derive our custom type from the WPF Line type which has X1, Y1, X2 and Y2 properties.

Ooopps... Line is sealed! (Shame on you twice).

Never mind, let's derive directly from the Shape base class, and add X1, Y1, X2, Y2 and two additional properties for defining the arrow's head width and height.


Our code should end up with something like this:

public sealed class Arrow : Shape
    public static readonly DependencyProperty X1Property = ...;
    public static readonly DependencyProperty Y1Property = ...;
    public static readonly DependencyProperty HeadHeightProperty = ...;

    public double X1
        get { return (double)base.GetValue(X1Property); }
        set { base.SetValue(X1Property, value); }

    public double Y1
        get { return (double)base.GetValue(Y1Property); }
        set { base.SetValue(Y1Property, value); }

    public double HeadHeight
        get { return (double)base.GetValue(HeadHeightProperty); }
        set { base.SetValue(HeadHeightProperty, value); }

    protected override Geometry DefiningGeometry
            // Create a StreamGeometry for describing the shape
            StreamGeometry geometry = new StreamGeometry();
            geometry.FillRule = FillRule.EvenOdd;

            using (StreamGeometryContext context = geometry.Open())

            // Freeze the geometry for performance benefits

            return geometry;

    /// <span class="code-SummaryComment"><summary></span>
    /// Draws an Arrow
    /// <span class="code-SummaryComment"></summary></span>
    private void InternalDrawArrowGeometry(StreamGeometryContext context)
        double theta = Math.Atan2(Y1 - Y2, X1 - X2);
        double sint = Math.Sin(theta);
        double cost = Math.Cos(theta);

        Point pt1 = new Point(X1, this.Y1);
        Point pt2 = new Point(X2, this.Y2);

        Point pt3 = new Point(
            X2 + (HeadWidth * cost - HeadHeight * sint),
            Y2 + (HeadWidth * sint + HeadHeight * cost));

        Point pt4 = new Point(
            X2 + (HeadWidth * cost + HeadHeight * sint),
            Y2 - (HeadHeight * cost - HeadWidth * sint));

        context.BeginFigure(pt1, true, false);
        context.LineTo(pt2, true, true);
        context.LineTo(pt3, true, true);
        context.LineTo(pt2, true, true);
        context.LineTo(pt4, true, true);

As you can see, it is very easy to implement a custom shape, thanks to the great work in the Shape base class. All we have to do is derive our custom shape type from Shape, and override the DefiningGeometry property. This property should return a Geometry of any type.

Points of Interest

My solution creates and returns a new frozen geometry on each call. Alternatively, you can cache a non frozen geometry by holding it as a field in the custom Shape class.

Final Words

Although custom shapes are very simple to implement, you can open Reflector or use the latest Microsoft .NET Framework code release to learn more about how the WPF team implemented WPF shapes.


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


About the Author

Tomer Shamam
Architect CodeValue
Israel Israel
Tomer Shamam is a Software Architect and a UI Expert at CodeValue, the home of software experts, based in Israel ( Tomer is a speaker in Microsoft conferences and user groups, and in-house courses. Tomer has years of experience in software development, he holds a B.A degree in computer science, and his writings appear regularly in the Israeli MSDN Pulse, and other popular developer web sites such as CodePlex and The Code Project. About his thoughts and ideas you can read in his blog (

You may also be interested in...

Comments and Discussions

Questionthanks Pin
masoudRajaei14-May-15 20:19
membermasoudRajaei14-May-15 20:19 
QuestionAny way to fill the arrow heads? Pin
Pankaj Nikam12-Jul-13 1:45
memberPankaj Nikam12-Jul-13 1:45 
AnswerRe: Any way to fill the arrow heads? Pin
starrynighter14-Jul-13 13:25
memberstarrynighter14-Jul-13 13:25 
GeneralRe: Any way to fill the arrow heads? Pin
Pankaj Nikam14-Jul-13 16:08
memberPankaj Nikam14-Jul-13 16:08 
QuestionSealed Pin
starrynighter4-Jul-13 16:40
memberstarrynighter4-Jul-13 16:40 
GeneralMy vote of 5 Pin
andyb19795-Sep-12 9:32
memberandyb19795-Sep-12 9:32 
GeneralWeird Code Error/Question... Pin
md5sum16-Oct-09 4:15
membermd5sum16-Oct-09 4:15 
GeneralRe: Weird Code Error/Question... Pin
Jaime Olivares20-Aug-11 20:22
memberJaime Olivares20-Aug-11 20:22 
GeneralAdd a arrow to the begin [modified] Pin
cmartinez3453-Sep-09 9:57
membercmartinez3453-Sep-09 9:57 
QuestionXAML? Pin
jc-denton2-Apr-09 8:46
memberjc-denton2-Apr-09 8:46 
AnswerRe: XAML? Pin
Tomer Shamam3-Apr-09 2:43
memberTomer Shamam3-Apr-09 2:43 
GeneralRe: XAML? Pin
jc-denton3-Apr-09 3:08
memberjc-denton3-Apr-09 3:08 
GeneralAnother Arrow realization Pin
targitaj22-Mar-09 22:12
membertargitaj22-Mar-09 22:12 
GeneralRe: Another Arrow realization Pin
Wiesław Šoltés31-Jul-11 3:47
memberWiesław Šoltés31-Jul-11 3:47 
GeneralIf you want a filled arrow... Pin
Simon Mourier4-Feb-09 23:55
memberSimon Mourier4-Feb-09 23:55 
GeneralRe: If you want a filled arrow... Pin
Pankaj Nikam15-Jul-13 17:18
memberPankaj Nikam15-Jul-13 17:18 
GeneralIncomplete Code and No Download Pin
Ray Hayes23-Jan-08 5:36
memberRay Hayes23-Jan-08 5:36 
AnswerRe: Incomplete Code and No Download Pin
Tomer Shamam23-Jan-08 5:41
memberTomer Shamam23-Jan-08 5:41 

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.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.180322.1 | Last Updated 27 Jan 2008
Article Copyright 2008 by Tomer Shamam
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid