|Wednesday, 07 April 2010|
Page 1 of 5
Creating a custom Shape isn't difficult but it does have some hidden gotchas. We take a look at how best to do the job.
One of the main workhorses of WPF 2D graphics is the Shape class - well to be more exact the classes that derive from it as the Shape class itself is abstract and cannot be instantiated. In many cases all you need to do to create 2D graphics is to simply create objects derived from Shape and add them to the Children collection of a suitable layout panel.
For example, to draw a line you would write something like:
This draws a black line from (0,0) to (50,50) with a width of 2. You can draw very complicated things using just the supplied Shape classes - Rectangle, Ellipse, Line, Polygon, PolyLine and Path.
Sometimes, however, it is easier to create your own custom Shape class derived directly from Shape. This makes it possible to add a complex graphic in one go rather than having to build it up Shape by Shape.
In what follows it is assumed that you know how to make use of one of the supplied classes derived from Shape, e.g. Line.
The Shape class provides all of the methods and properties you need to implement a Framework element and participate in the WPF layout process. It also comes with a range of properties that control the "look" of the graphic - stroke-related and fill-related.
One thing that is puzzling about WPF 3D graphics is that it seems to have the same facilities provided by different classes but when you look more carefully you quickly realise that there is a hierarchy of implementation.
For example why have both the Line and LineGeometry classes?
The answer is that LineGeometry defines an abstract geometrical line - it doesn't have any color or width. The Line class however is derived from Shape and represents a line that can be drawn on the screen with a given color and thickness - determined by the Stroke and StrokeThickness properties.
The relationship between the two is made even clearer when you know that the Line class has a LineGeometry object as one of its internal properties that determines where the Stroke and StrokeThickness properties will be applied to create a visible line on the screen.
Line doesn't inherit from LineGeometry but it contains an instance of LineGeometry.
To see how this all works let's create our own version of the Line class. To do this we need to create a class that inherits from Shape. The Shape class has a protected method called DefiningGeometry. This is used internally by the Shape class to retrieve the geometry that the stroke and fill properties are applied to to produce the rendered graphic.
That is to create a new shape all you have to do is override the DefiningGeometry method and make it return a Geometry object that defines the graphic.
For example, in the case of our re-implementation of the Line class we would write something like:
public class MyLine : Shape
In this example we create an instance of LineGeometry specifying a line between (0,0) and (50,50). To see the line we simply create an instance and add it to the layout.
Notice that we can apply Stroke and StrokeThickness as these are properties inherited from the Shape class and when MyLine is rendered the class applies these properties to the Geometry object returned by DefiningGeometry.
It really is this simple - but there are some facilities not inherited from Shape and there are some difficulties of implementation that we need to examine in more detail.
If you examine the properties that are inherited from Shape, in say the MyLine class, you will discover that they don't include the familiar X1, Y1, X2 and Y2 used to position the end points of a Line. In fact no positioning properties or positioning methods are included in Shape. Where the graphic is to be rendered is entirely determined by the Geometry returned by DefiningGeometry.
What this means is that its up to you to implement a positioning and if required a sizing mechanism in your derived class - and this can be difficult for reasons that will become apparent.
However, adding positioning to MyLine is comparatively easy. All we need to do is to add the necessary properties and create a LineGeometry object with the correct start and end point:
public class MyLine : Shape
If you now try positioning the line explicitly you will find it works:
|Last Updated ( Wednesday, 07 April 2010 )|