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

A Graphics Drawing Tool by using C# and Winforms

0.00/5 (No votes)
23 Jun 2011 3  
A Graphics Drawing Tool by using C# and Winforms

Introduction

This is a drawing tool I developed in my spare time, it can draw rectangle, circle, and other shapes, you can also move, resize, even rotate some of the shapes. It will be a useful starting point of a complicated graphics project.

Background

To fully understand this code, the user has to understand some of the C# concepts, reflection, interfaces, inheritance, etc. There is another version of this drawing tool by using WPF, you can find it on GraphicsDrawingToolWPF.aspx.

Using the Code

This project first creates a tool box which contains all the available drawing tools. Like the following screen capture:

Untitled-2.jpg

Then the user can select these tools to draw on main screen, a screen capture like the following:

Untitled-3.jpg

This project also comes up with a propertybag for user, to dynamically change shape border color, fill color, arrow width, text box content, text size, etc.

Untitled-4.jpg

This project finally provides the user with an option of exporting the drawing as XML file or jpg file.

<?xml version="1.0" encoding="utf-8"?>
<ShapeList xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <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>

With the above XML file, the project can open this file next time, users can keep editing their drawings.

Once finished, users can choose export it to jpg file.

Following is the explanation of the project.

I have defined several basic shapes, the LeShape is basic class, in order to serialize the shapes I have created, I have redefined LeColor structure, like the following:


public struct LeColor
    {
        public int A;
        public int R;
        public int G;
        public int B;
        public LeColor(Color color)
        {
            this.A = color.A;
            this.R = color.R;
            this.G = color.G;
            this.B = color.B;
        }

        public static LeColor FromColor(Color color)
        {
            return new LeColor(color);
        }

        public Color ToColor()
        {
            return Color.FromArgb(A, R, G, B);
        }
    }
         

As we can't serialize C# Font and Color class to XML, I created their equivalent structures and used them everywhere.

This drawing tool's basic class is LeShape:

    public abstract class LeShape : IShape
    {
        private bool showBorder = true;
        public bool ShowBorder
        {
            get { return showBorder; }
            set
            {
                showBorder = value;
                LeCanvas.self.Canvas.Invalidate();
            }
        }
        private LeColor borderColor = new LeColor(Color.Black);
        public LeColor LeBorderColor
        {
            get { return borderColor; }
            set
            {
                borderColor = value;
                LeCanvas.self.Canvas.Invalidate();
            }
        }

        [XmlIgnore]
        public Color BorderColor
        {
            get { return LeBorderColor.ToColor(); }
            set { LeBorderColor = new LeColor(value); }

        }
        private int borderWidth = 1;
        public int BorderWidth
        {
            get { return borderWidth; }
            set
            {
                borderWidth = value;
                LeCanvas.self.Canvas.Invalidate();
            }
        }

        private Rectangle bounds;
        [XmlIgnore]
        public Rectangle Boundary
        {
            set { bounds = value; 
            Rect =new LeRect(value); 
            }
            get { return bounds; }
        }

...
        public LeShape()
        {
            path = new GraphicsPath();
            objectsInPath = new ArrayList();
        }

As you can see, this LeShape class is abstract class, as we don't want user to instantiate it at any time. Instead we create ZoneShape, Rectangle shape classes based on this LeShape, then we instantiate them, it makes more sense.

In order to let user move, resize shapes, I made another class BoundaryShape, it inherits from LeShape, which contains all the properties, while this BoundaryShape only handles user's mouse movement, and it won't be serialized to XML file.

Basically all the shapes will be inherited from BoundaryShape, and BoundaryShape inherited from LeShape.

public class RoundRectShape : BoundaryShape
{
        private int radius = 10; 

We can have corner radius shape, by default radius is 10 px.

We used the following paint method to draw this cornered Rectangle shape.

    public override void Paint(object sender, Graphics g)
        {
            Point[] pt = new Point[8];
 
            path = new GraphicsPath();

          path.AddLine(pt[4], pt[5]);
            path.AddArc(new Rectangle(pt[6], new Size(radius, radius)),
                90, 90);
            path.AddLine(pt[6], pt[7]);

            if (path != null)
            {
                g.FillPath(new System.Drawing.Drawing2D.LinearGradientBrush(
                    Boundary, FromColor, ToColor, LightAngle), path);
            }
        }

This paint method has a copy in BoundaryShape, we don't want to use it, therefore we put override as modifier.

You will also see the LeShape implemented IShape interface.

I made LeShape's IShape implementation virtual method. Then at its inherited classes, selectively rewrite these virtual methods.

ZoneShape has a text field, the idea is when ZoneShape moves, text field moves as well. This is achieved when user has finished move zoneshape, then raise an event at BoundaryShape, ZoneShape accepts this event then processes this event, moves the text field parameters.

User's mouse movement was handled by LeCanvas class, LeCanvas class then passes this event to all its on screen shapes. Each shape then decides its actions. Either it's a drawing start, or move a shape or resize a shape.

End Note

I wanted to contribute to the computer world, where I always enjoyed learning from others, or for others that maybe think my work is useful.

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