Title: Make drawing extension methods in WPF and C#
This example shows how to make a few handy extension methods for drawing in WPF. Drawing in WPF is at best awkward and at worst downright painful.
For example, the following XAML code draws a rectangle.
<Grid>
<Canvas Name="canDrawing">
<Rectangle Canvas.Left="100" Canvas.Top="30"
Width="100" Height="70" Fill="LightGreen"
Stroke="Green" StrokeThickness="5"/>
</Canvas>
</Grid>
This is awkward but survivable. The following code shows how you can use C# code to draw the same rectangle.
Rectangle rectangle = new Rectangle();
Canvas.SetLeft(rectangle, 100);
Canvas.SetTop(rectangle, 30);
rectangle.Width = 100;
rectangle.Height = 70;
rectangle.Fill = Brushes.LightGreen;
rectangle.Stroke = Brushes.Green;
rectangle.StrokeThickness = 5;
canDrawing.Children.Add(rectangle);
This code creates a new Rectangle object. The Left and Top properties are attached properties provided by the Canvas class, not properties of the rectangle. That means you cannot simply set the properties for the rectangle. Instead you must use the Canvas class's SetLeft and SetTop methods to set those values. (I get why they did it this way. The Left and Top properties only make sense if the rectangle is inside some other control, but why didn't they just give shape objects their own properties?)
The code then sets the rectangle's Width, Height, Fill, Stroke, and StrokeThickness properties. Finally, the program adds the rectangle to the Canvas control's child list.
All of this is manageable, but awkward. It would be nice if the Canvas class provided a method for creating a Rectangle object and setting at least some of its properties.
The following code shows an extension method that does that.
public static class DrawingExtensions
{
// Add a Rectangle to a Canvas.
public static Rectangle DrawRectangle(
this Canvas canvas, Brush fill, Brush stroke,
double stroke_thickness, double left,
double top, double width, double height)
{
Rectangle rectangle = new Rectangle();
Canvas.SetLeft(rectangle, left);
Canvas.SetTop(rectangle, top);
rectangle.Width = width;
rectangle.Height = height;
rectangle.Fill = fill;
rectangle.Stroke = stroke;
rectangle.StrokeThickness = stroke_thickness;
canvas.Children.Add(rectangle);
return rectangle;
}
}
Recall that extension methods must be contained in public static classes. that's why this method is inside the DrawingExtensions class.
The method must also be static. Its first parameter is marked with the this keyword to indicate that the method extends that object. In this case, the method extends the Canvas class.
The body of the method simply performs the same steps shown earlier that use C# code to create a new Rectangle object, set its parameters, and add it to the Canvas control's Children collection.
The method finishes by returning the new Rectangle object in case the calling code needs to manipulate it. For example, that code could change the object's properties or set other properties.
The main program can use this method as in the following.
rect = new Rect(50, 80, 200, 20);
rectangle = canDrawing.DrawRectangle(Brushes.Pink,
Brushes.Red, 5, 100, 30, 100, 70);
rectangle.RadiusX = 10;
rectangle.RadiusY = 10;
This code uses the extension method to create the Rectangle. It then changes the returned Rectangle object's RadiusX and RadiusY properties to give the rectangle rounded corners.
Sometimes it may be convenient to define a Rectangle by giving a Rect that defines its size and position. The following code shows an extension method
public static Rectangle DrawRectangle(
this Canvas canvas, Brush fill, Brush stroke,
double stroke_thickness, Rect rect)
{
return canvas.DrawRectangle(fill, stroke,
stroke_thickness, rect.Left, rect.Top,
rect.Width, rect.Height);
}
This method simply invokes the previous version of the extension method, using the Rect parameter's properties to fill in the rectangle's size and position.
The example program also defines extension methods to draw ellipses. Those methods are so similar to the DrawRectangle extension methods that I won't show them here. Download the example to see the details.
The following code shows the DrawLine extension method.
public static Line DrawLine(
this Canvas canvas, Brush stroke,
double stroke_thickness, double x1, double y1,
double x2, double y2)
{
Line line = new Line();
line.X1 = x1;
line.Y1 = y1;
line.X2 = x2;
line.Y2 = y2;
line.Stroke = stroke;
line.StrokeThickness = stroke_thickness;
canvas.Children.Add(line);
return line;
}
This method creates a Line object and sets its X1, Y1, X2, and Y2 properties. It sets the stroke properties and returns the new object.
The following code shows the DrawPolyline extension method.
public static Polyline DrawPolyline(
this Canvas canvas, Brush stroke,
double stroke_thickness,
IEnumerable<Point> points)
{
Polyline polyline = new Polyline();
polyline.Points = new PointCollection(points);
polyline.Stroke = stroke;
polyline.StrokeThickness = stroke_thickness;
canvas.Children.Add(polyline);
return polyline;
}
This method is similar to the others. It creates the new drawing object, sets its properties, and returns the new object. This method is a bit different because it needs to define object's Points property. That property must be set to a PointCollection object. Fortunately that object's constructor can take an IEnumerable<Point> as a parameter so initializing the points is easy.
Most of the example's main program is simple because it uses these extension methods. For example, the following code shows how the program makes the face's left eye and pupil.
// Left eye.
rect = new Rect(100, 110, 40, 50);
canDrawing.DrawEllipse(Brushes.White,
Brushes.Black, 5, rect);
// Left pupil.
rect = new Rect(120, 120, 20, 30);
canDrawing.DrawEllipse(Brushes.Black,
Brushes.Black, 5, rect);
Download the example to experiment with it and to see additional details.
|