Introduction
This is an article about WPF and its drawing tool.
The code structure is similar as that of my other article at code project, the link is at:
WinForm Versions of GraphicsDrawingTool.aspx
Background
To understand this article, you need to understand a few WPF technologies. Concept such as DrawingContext, FrameworkElement class, and their usages, and of course how to write XAML GUI stuff.
Using the code
This project first create a tool box, like following:
Then user can draw their selected shape on screen, like following:
The drawing then can be exported as xml file or jpg file.
="1.0" ="utf-8"
<ShapeList xmlns:xsi="<a href="http://www.w3.org/2001/XMLSchema-instance">http://www.w3.org/2001/XMLSchema-instance</a>" xmlns:xsd="<a href="http://www.w3.org/2001/XMLSchema">http://www.w3.org/2001/XMLSchema</a>">
<ShapeList>
<LeShape xsi:type="LeRectangle">
<ShowBorder>true</ShowBorder>
<LeBorderColor>
<A>255</A>
<R>0</R>
<G>0</G>
<B>0</B>
</LeBorderColor>
<BorderWidth>1</BorderWidth>
<Rect>
<X>300</X>
<Y>157</Y>
<Width>79</Width>
<Height>65</Height>
</Rect>
<LeFromColor>
<A>30</A>
<R>255</R>
<G>0</G>
<B>0</B>
</LeFromColor>
<LeToColor>
<A>30</A>
<R>255</R>
<G>255</G>
<B>255</B>
</LeToColor>
<LightAngle>225</LightAngle>
<Fill>true</Fill>
</LeShape>
<LeShape xsi:type="RoundRectShape">
<ShowBorder>true</ShowBorder>
<LeBorderColor>
<A>255</A>
<R>0</R>
<G>0</G>
<B>0</B>
</LeBorderColor>
<BorderWidth>1</BorderWidth>
<Rect>
<X>174</X>
<Y>230</Y>
<Width>84</Width>
<Height>74</Height>
</Rect>
<LeFromColor>
<A>255</A>
<R>0</R>
<G>0</G>
<B>0</B>
</LeFromColor>
<LeToColor>
<A>255</A>
<R>127</R>
<G>255</G>
<B>212</B>
</LeToColor>
<LightAngle>225</LightAngle>
<Fill>true</Fill>
<Radius>10</Radius>
</LeShape>
<LeShape xsi:type="ZoneShape">
<ShowBorder>true</ShowBorder>
<LeBorderColor>
<A>255</A>
<R>0</R>
<G>0</G>
<B>0</B>
</LeBorderColor>
<BorderWidth>1</BorderWidth>
<Rect>
<X>132</X>
<Y>97</Y>
<Width>90</Width>
<Height>84</Height>
</Rect>
<LeFromColor>
<A>30</A>
<R>255</R>
<G>0</G>
<B>0</B>
</LeFromColor>
<LeToColor>
<A>30</A>
<R>255</R>
<G>255</G>
<B>255</B>
</LeToColor>
<LightAngle>225</LightAngle>
<Fill>true</Fill>
<TextField>
<ShowBorder>true</ShowBorder>
<LeBorderColor>
<A>255</A>
<R>0</R>
<G>0</G>
<B>0</B>
</LeBorderColor>
<BorderWidth>1</BorderWidth>
<Rect>
<X>237</X>
<Y>112</Y>
<Width>58</Width>
<Height>22</Height>
</Rect>
<LeFromColor>
<A>30</A>
<R>255</R>
<G>0</G>
<B>0</B>
</LeFromColor>
<LeToColor>
<A>30</A>
<R>255</R>
<G>255</G>
<B>255</B>
</LeToColor>
<LightAngle>225</LightAngle>
<Fill>true</Fill>
<Caption>Shape 2</Caption>
<LeTextFont>
<Size>10</Size>
<Name>Tahoma</Name>
<Style>Regular</Style>
</LeTextFont>
<LeTextColor>
<A>255</A>
<R>255</R>
<G>0</G>
<B>0</B>
</LeTextColor>
<TextSize>10</TextSize>
</TextField>
<Caption>Shape 2</Caption>
</LeShape>
</ShapeList>
</ShapeList>
This xml file then can be reopened later by this project, end user can edit their drawings again.
Following is the explaination of this project's source code.
The project first create a canvas on GUI by using following lines:
<Border Margin="10" CornerRadius="3" Grid.Row="1" Grid.Column="1" Background="Beige">
<Border.BitmapEffect>
<DropShadowBitmapEffect />
</Border.BitmapEffect>
<Canvas Margin="3" x:Name="DrawingCanvas" Background="AliceBlue" Opacity="1" Visibility="Visible">
<local:CustomRender Canvas.Top="0" Canvas.Left="0" x:Name="shapeCollection">
<local:CustomRender.BitmapEffect>
<DropShadowBitmapEffect />
</local:CustomRender.BitmapEffect>
</local:CustomRender>
</Canvas>
</Border>
The code above first create a Border, with a drop shadow, Border was inside a Grid control, so it will fill the grid's cell. Then what's inside of this border, it's a canvas control, what's inside this Canvas is just one framework element, our DLL (CustomRender), it is just one element, then we use this element's DrawingContext, draw all our shapes manually. The DrawingContext is like WinForm's Graphics object.
Our customrender is just a framework element, it only accept visuals objects. Most importantly it implemented following 2 functions:
protected override int VisualChildrenCount
{
get { return childrens.Count; }
}
protected override Visual GetVisualChild(int index)
{
if (index < 0 || index >= childrens.Count)
{
throw new ArgumentOutOfRangeException();
}
return childrens[index];
}
Then at our code, we only need to add visual object to this collections of visual object, .netframework will render this CustomRender object for us.
We used reflection to add shape to our controller class, first only create a shape, then add shape's visual object to above CustomRender class.
Point pt = e.GetPosition(myCanvas);
ConstructorInfo constructor = myTool.GetConstructor(new Type[] { typeof(Point) });
CurShape = constructor.Invoke(new object[] { pt }) as LeShape;
shapeCollection.AddObject(CurShape.myVisual);
When we want to draw this Shape, we can call following method at anytime:
DrawingContext dc = myVisual.RenderOpen();
Draw(dc);
if (selected)
{
if (bounds.Width > 5 && bounds.Height >5)
{
DrawPoints(dc, bounds);
}
}
dc.Close();
RenderOpen method of DrawingVisual will open a DrawingContext for us, then we can Draw our object, if shape is selected, then I draw several track points for it. After all these, we have to call DrawingContext Close method, this to tell we have finished drawing of this visual.
This the principles of WPF version of my drawing tool, if you are intertested you can get more information on my WinForms version of this drawing tool as well.
Points of Interest
Did you learn anything interesting/fun/annoying while writing the code? Did you do anything particularly clever or wild or zany?
History
Keep a running update of any changes or improvements you've made here.