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.. 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)
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 = ...; ... [TypeConverter(typeof(LengthConverter))] public double X1 { get { return (double)base.GetValue(X1Property); } set { base.SetValue(X1Property, value); } } [TypeConverter(typeof(LengthConverter))] public double Y1 { get { return (double)base.GetValue(Y1Property); } set { base.SetValue(Y1Property, value); } } [TypeConverter(typeof(LengthConverter))] public double HeadHeight { get { return (double)base.GetValue(HeadHeightProperty); } set { base.SetValue(HeadHeightProperty, value); } } ... protected override Geometry DefiningGeometry { get { // Create a StreamGeometry for describing the shape StreamGeometry geometry = new StreamGeometry(); geometry.FillRule = FillRule.EvenOdd; using (StreamGeometryContext context = geometry.Open()) { InternalDrawArrowGeometry(context); } // Freeze the geometry for performance benefits geometry.Freeze(); return geometry; } } /// <summary> /// Draws an Arrow /// </summary> 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.
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.
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.
General
News
Question
Answer
Joke
Rant
Admin