Sometimes you need to draw various shapes, drawings and objects using the same elaborate pen. Like the rectangle and the star in this picture.
Problem is that some WPF elements like GeometryDrawing (represented by star in this picture) take Pen object for their pen settings and classes derived from Shape (like Rectangle, Ellipse, etc.) expose all the pen settings directly in the form of StrokeXxx properties. So when you want to draw several GeometryDrawings and several shapes using the same pen settings you end up defining 2 sets of these settings in your Resources: one in the form of a Pen object and 1 for StrokeXxx properties.
In this article we will rectify this problem with the help of one simple extension method and an attached property.
ApplyPen() Method
First we create ApplyPen() extension method for the Shape class. All it does is assigns Pen properties to the appropriate StrokeXxx properties of the Shape. Here's the code:
1: public static void ApplyPen(this Shape shape, Pen pen)
2: {
3: if (pen != null)
4: {
5: shape.Stroke = pen.Brush;
6: shape.StrokeThickness = pen.Thickness;
7: shape.StrokeDashCap = pen.DashCap;
8: if (pen.DashStyle != null)
9: {
10: shape.StrokeDashArray = pen.DashStyle.Dashes;
11: shape.StrokeDashOffset = pen.DashStyle.Offset;
12: }
13: shape.StrokeStartLineCap = pen.StartLineCap;
14: shape.StrokeEndLineCap = pen.EndLineCap;
15: shape.StrokeLineJoin = pen.LineJoin;
16: shape.StrokeMiterLimit = pen.MiterLimit;
17: }
18: }
Attached Property – Pen
Using ApplyPen() method we can apply any pen to any shape from code but we want to be able to do this from XAML too. So we define an attached property called Pen which when set calls our extension method to apply the pen. Here's how it's implemented:
1: public static readonly DependencyProperty PenProperty = DependencyProperty.RegisterAttached(
2: "Pen", typeof(Pen), typeof(ShapeExtender),
3: new FrameworkPropertyMetadata(null, new PropertyChangedCallback(ShapeExtender.PenProperty_Changed))
4: );
5:
6: public static void SetPen(Shape shape, Pen pen)
7: {
8: shape.SetValue(PenProperty, pen);
9: }
10:
11: public static Pen GetPen(Shape shape)
12: {
13: return (Pen)shape.GetValue(PenProperty);
14: }
15:
16: private static void PenProperty_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
17: {
18: Shape shape = d as Shape;
19: if (shape != null)
20: {
21: shape.ApplyPen(e.NewValue as Pen);
22: }
23: }
Now we can define a single Pen in our resources and use it for both Rectangle and star (GeometryDrawing) in our sample.
Pen definition:
1: <Window.Resources>
2: <Pen x:Key="MyPen" Thickness="10" LineJoin="Bevel">
3: <Pen.Brush>
4: <LinearGradientBrush>
5: <GradientStop Color="#FF5555" Offset="0" />
6: <GradientStop Color="#FFFF55" Offset="0.2" />
7: <GradientStop Color="#55FFFF" Offset="0.4" />
8: <GradientStop Color="#FF55FF" Offset="0.6" />
9: <GradientStop Color="#5555FF" Offset="0.8" />
10: <GradientStop Color="#55FF55" Offset="1" />
11: </LinearGradientBrush>
12: </Pen.Brush>
13: <Pen.DashStyle>
14: <DashStyle Dashes="0.2 2 2 0.2" />
15: </Pen.DashStyle>
16: </Pen>
17: </Window.Resources>
Pen applied to both GeometryDrawing and a Rectangle:
1: <Rectangle Name="Rectangle1" Canvas.Left="20" Canvas.Top="20" Width="200" Height="100"
2: local:ShapeExtender.Pen="{StaticResource MyPen}"
3: >
4: </Rectangle>
5:
6: <Image Canvas.Left="250" Canvas.Top="20" Height="100" Width="100">
7: <Image.Source>
8: <DrawingImage>
9: <DrawingImage.Drawing>
10: <GeometryDrawing Pen="{StaticResource MyPen}">
11: <GeometryDrawing.Geometry>
12: <PathGeometry>
13: <PathFigure StartPoint="20,100" IsClosed="True">
14: <LineSegment Point="100,40" />
15: <LineSegment Point="0,40" />
16: <LineSegment Point="80,100" />
17: <LineSegment Point="50,0" />
18: </PathFigure>
19: </PathGeometry>
20: </GeometryDrawing.Geometry>
21: </GeometryDrawing>
22: </DrawingImage.Drawing>
23: </DrawingImage>
24: </Image.Source>
25: </Image>
Bonus: GetPenFromStroke() Method
Using ApplyPen() method and Pen attached property we can apply a Pen to a Shape. But what if we need to go the other way around? For example if you draw something from code using DrawingContext.DrawXxx() methods you need a Pen. And sometimes you want this pen to match settings of some Shape. So you need the opposite of ApplyPen() method – a method that takes a Shape and returns a Pen based on it's StrokeXxx settings. Here is a super-simple but nevertheless useful GetPenFromStroke() extension method:
1: public static Pen GetPenFromStroke(this Shape shape)
2: {
3: return new Pen()
4: {
5: Brush = shape.Stroke,
6: Thickness = shape.StrokeThickness,
7: DashCap = shape.StrokeDashCap,
8: DashStyle = new DashStyle()
9: {
10: Dashes = shape.StrokeDashArray,
11: Offset = shape.StrokeDashOffset
12: },
13: StartLineCap = shape.StrokeStartLineCap,
14: EndLineCap = shape.StrokeEndLineCap,
15: LineJoin = shape.StrokeLineJoin,
16: MiterLimit = shape.StrokeMiterLimit
17: };
18: }
