Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A Graphics Drawing Tool by using WPF

0.00/5 (No votes)
23 Jun 2011 2  
Graphics Drawing Tool by WPF

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:

wpf2.jpg

Then user can draw their selected shape on screen, like following:

wpf1.jpg

The drawing then can be exported as xml file or jpg file.

<?xml version="1.0" encoding="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:

// Provide a required override for the VisualChildrenCount property.
        protected override int VisualChildrenCount
        {
            get { return childrens.Count; }
        }
       // Provide a required override for the GetVisualChild method.
        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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here