It's become rather routine these days for authors of WPF books to create diagrams for the book with XAML files. I'll be doing this with my forthcoming WPF 3D book, and the XAML files associated with these diagrams will be available with the rest of the source code.
Several diagrams in the book involve vectors, which are customarily pictured as lines with arrows on the end, and for these I used some ad hoc solutions. In one XAML file I created a little filled triangle, and positioned it on the heads of the line and rotated it appropriately. In another XAML file, I drew all lines using Polyline with four strokes: one for the line itself, another for one half of the arrow, then back up to the tip, and then another for the other half of the arrow. I adjusted the coordinates in XAML Cruncher until the arrow looked OK.
But all the time I was assembling these arrows I kept thinking: You know, you should really create a class that does all the math for you. Then I got an email suggesting I write a blog entry about arrowed lines. So here it is.
The Arrowheads.zip file contains a demo program and two classes named ArrowLine and ArrowPolyline that derive from Shape by way of an abstract class ArrowLineBase. ArrowLineBase defines four properties:
- ArrowAngle is the angle between the two strokes of the arrowhead. The default is 45 degrees.
- ArrowLength is the length of the two strokes of the arrowhead. The default is 12, or 1/8 inch.
- ArrowEnds indicates whether the arrowhead appears on the beginning of the line, the end of the line, or both. You set this to an ArrowEnds enumeration value: Start, End (the default), Both, or None.
- IsArrowClosed is a Boolean (default false) that indicates if the two sides of the arrowhead are joined to form a triangle.
All these properties are backed by dependency properties so you can reference them in animations, bindings, and styles.
The ArrowLine class derives from ArrowLineBase and basically duplicates the Line class by defining X1, Y1, X2, and Y2 properties; ArrowPolyline duplicates the Polyline class by defining a Points property.
I remember trying to derive from Shape a long time ago and being somewhat confused. This time it seemed relatively straightforward. I defined all the dependency properties with FrameworkPropertyMetadata objects and a flag of AffectsMeasure. This flag forces a new measure and render pass whenever a property changes. In ArrowLine and ArrowPolyline I overrode the get accessor of the DefiningGeometry property and simply returned a PathGeometry object describing the figure. Apparently code in Shape itself accesses this property during its OnRender method.
The DefiningGeometry properties in ArrowLine and ArrowPolyline work in conjunction with the DefiningGeometry property in ArrowLineBase. The properties in the two non-abstract classes fill a PolyLineSegment with the line or polyline definition; the property then calls the base property in the abstract ArrowLineBase class to add one, two, or no additional PolyLineSegment objects for the arrows.
Because the arrows are basically part of the line, they are affected by all the properties that affect the line, such as Stroke, StrokeThickness, StrokeStartLineCap, and StrokeLineJoin. If you set IsArrowClosed to true, the Fill property comes into play; the arrowhead will look most normal if Fill is set to the same brush as Stroke.
The namespace I've used with these classes is Petzold.Media2D because I'll be transferring them over to a DLL that I'm assembling for the 3D book. Virtually everything in that DLL will be in the Petzold.Media3D namespace (and that's what the DLL will be named) but I'll have a few 2D enhancements as well. I hope to make an early version of that DLL available in a few weeks.