[C# Helper]
Index Books FAQ Contact About Rod
[Essential Algorithms, Second Edition]

[The Modern C# Challenge]

[WPF 3d, Three-Dimensional Graphics with WPF and C#]

[The C# Helper Top 100]

[Interview Puzzles Dissected]

[C# 24-Hour Trainer]

[Beginning Software Engineering]

[C# 5.0 Programmer's Reference]

[MCSD Certification Toolkit (Exam 70-483): Programming in C#]

Title: Create drawing extension methods in WPF and C#

[Create drawing extension methods in WPF and C#]

One of the many annoying things about WPF is the primitive constructors that Microsoft provided for creating controls and shapes. Each has a constructor that takes no parameters. You then need to initialize each property individually even though many of those properties are pretty much required for the object to be useful. For example, the following code shows how you might typically create a Rectangle.

Rectangle rectangle = new Rectangle(); rectangle.Stroke = Brushes.Blue; rectangle.StrokeThickness = 2; rectangle.Fill = Brushes.LightBlue; rectangle.Width = 50; rectangle.Height = 200; Canvas.SetLeft(rectangle, 75); Canvas.SetTop(rectangle, 30); canControls.Children.Add(rectangle);

Not only is this unnecessarily long-winded, but it also assumes that you know all of the properties provided by the Rectangle class so you know which ones you need to set. (Plus the weird method call that you need to make to set the Canvas.Left and Canvas.Top attached properties.) Until you have all of that memorized, you won't know what properties to use so creating shapes in a royal pain. A nice constructor that let you set those properties would prompt you so you would know which properties to set. (These methods add shapes to the Canvas control because it's relatively easy to position drawing controls there.)

This example adds some extension methods to the Canvas class to make it easier to create these objects.

For example, here's the way you can create a Rectangle similar to the one above.

canControls.DrawRectangle(75, 30, 50, 200, Brushes.LightBlue, Brushes.Blue);

The names of the parameters (and XML help if I wasn't too lazy to add it) let you know what each of the parameters does.

The following sections describe pieces of the example project.

Support Methods

(Recall that extension methods must be defined inside a public static class. To make pieces of code easy to reuse for different objects, such as Rectangle and Ellipse, I wrote some support methods.

The following SetBounds method sets a Shape object's Left and Top attached properties, and its Width, and Height properties.

private static void SetBounds(this Shape shape, double left, double top, double width, double height) { Canvas.SetLeft(shape, left); Canvas.SetTop(shape, top); shape.Width = width; shape.Height = height; }

The following overloaded version of SetBounds lets you define the bounds by passing a Rect into the method.

private static void SetBounds(this Shape shape, Rect rect) { shape.SetBounds(rect.Left, rect.Top, rect.Width, rect.Height); }

The following PrepareGraphicProperties method sets an object's graphical properties Fill, Stroke, StrokeThickness, and StrokeDashArray.

private static void PrepareGraphicProperties(this Shape shape, Brush fill, Brush stroke, double stroke_thickness, DoubleCollection stroke_dash_array) { shape.Fill = fill; shape.Stroke = stroke; shape.StrokeThickness = stroke_thickness; if (stroke_dash_array != null) { shape.StrokeDashArray = stroke_dash_array; } }

This method extends the Shape class. It simply sets the shape's Fill, Stroke, and StrokeThickness properties. Then if the stroke_dash_array parameter is not null, it also sets the shape's StrokeDashArray property.

Now the following PrepareShape method uses the SetBounds and PrepareGraphicProperties methods to prepare a shape for use.

private static void PrepareShape(this Shape shape, double left, double top, double width, double height, Brush fill, Brush stroke, double stroke_thickness, DoubleCollection stroke_dash_array) { shape.SetBounds(left, top, width, height); shape.PrepareGraphicProperties(fill, stroke, stroke_thickness, stroke_dash_array); }

The following code shows the remaining support methods.

public static Point GetCenter(this Shape shape) { double cx = (Canvas.GetLeft(shape) + Canvas.GetRight(shape)) / 2.0; double cy = (Canvas.GetTop(shape) + Canvas.GetBottom(shape)) / 2.0; return new Point(cx, cy); } public static Rect GetBounds(this Shape shape) { return new Rect( Canvas.GetLeft(shape), Canvas.GetTop(shape), shape.Width, shape.Height); } // Position shape1 over shape2. public static void MatchBounds(this Shape shape1, Shape shape2) { shape1.SetBounds(shape2.GetBounds()); }

The GetCenter method returns the object's center.

GetBounds returns a Rect giving the object's center.

MatchBounds resizes and moves object shape1 so it has the same position and bounds as shape2. For example, you could use this to position a label on top of an ellipse or rectangle.

Notice how this method uses the overloaded version of SetBounds that takes a Rect as a parameter.

Rectangles

With those helper methods in place, it's fairly easy to make extension methods that create Rectangle objects. The following method creates a new Rectangle object on a Canvas control.

public static Rectangle DrawRectangle(this Canvas canvas, double left, double top, double width, double height, Brush fill, Brush stroke, double stroke_thickness, DoubleCollection stroke_dash_array) { Rectangle rectangle = new Rectangle(); rectangle.PrepareShape(left, top, width, height, fill, stroke, stroke_thickness, stroke_dash_array); canvas.Children.Add(rectangle); return rectangle; }

This method simply creates a new Rectangle, calls PrepareShape to set its properties, adds it to the Shape control's Children collection, and returns the result so you can use it in your program if you like.

The example also defines two overloaded versions that don't take dash and thickness parameters in case you want to use defaults for those.

In addition to drawing rectangles, the example defines extension methods to draw the following shapes:

  • Rectangle
  • Ellipse
  • Polygon
  • Polyline
  • Label
  • Line
  • Dot

The next section says more about dots.

Dots

You can download the example to see how it draws other shapes, but I want to give a little more details for dots. It seems like I often need to draw a dot: a circle with a given radius and centered at a position. The following method makes that easy.

public static Ellipse DrawDot(this Canvas canvas, double cx, double cy, double radius, Brush fill, Brush stroke, double stroke_thickness, DoubleCollection stroke_dash_array) { return canvas.DrawEllipse( cx - radius, cy - radius, 2.0 * radius, 2.0 * radius, fill, stroke, stroke_thickness, stroke_dash_array); }

This code simply calls the DrawEllipse extension method, passing it the necessary parameters. That method is similar to the DrawRectangle method decsribed earlier, so I won't show it here.

The other thing I often do (and the inspiration for this post) is to draw a label inside a circle. The following overloaded version of DrawDot does that.

public static Ellipse DrawDot(this Canvas canvas, out Label label, double cx, double cy, double radius, Brush fill, Brush stroke, double stroke_thickness, DoubleCollection stroke_dash_array, string text, string font_family, double font_size, FontWeight font_weight, FontStyle font_style, Brush border_brush, Thickness border_thickness, Brush background_brush, Brush foreground_brush) { Ellipse ellipse = canvas.DrawDot( cx, cy, radius, fill, stroke, stroke_thickness, stroke_dash_array); label = canvas.DrawLabel( cx - radius, cy - radius, 2.0 * radius, 2.0 * radius, text, font_family, font_size, font_weight, font_style, border_brush, border_thickness, background_brush, foreground_brush, HorizontalAlignment.Center, VerticalAlignment.Center); return ellipse; }

This method calls the previous version of DrawDot and saves the result. It then calls the DrawLabel extension method (not shown) to draw a label with the same bounds as the Ellipse that it just drew. It returns the Ellipse and returns the Label through an out parameter.

Summary

These extension methods make it easier to add shapes to a Canvas control. The example includes methods for adding Ellipse, Rectangle, Polygon, Polyline, Label, Line, and Dot shapes. Feel free to use the example as a template for making your own methods to easily add other shapes or controls.

Download the example to experiment with it and to see additional details.

© 2009-2022 Rocky Mountain Computer Consulting, Inc. All rights reserved.