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

Interactively Add Multiple Shapes Using Linked Lists GDI+ SVG

0.00/5 (No votes)
3 Mar 2016 25  
Tutorial 2 (how to interactively add multiple shapes GDI using linked lists)

This is the second tutorial from a series of tutorials heading towards creating a fully functional graphics program that exports its artwork in svg, png. We can also implement this to be a circuit program and to output its work in a verilog format, I recommend you read my first tutorial http://www.codeproject.com/script/Articles/MemberArticles.aspx?amid=10828711.

What you will be capable of building:

Introduction

Today, we will discuss how to enable the user to add different shapes to the program. We will implement this using Linked List in C#, which would be more practical to use than normal lists... simply because you don't need to put boundaries on the number of shapes to be added by the user.

General Plan

  1. Adding different shapes (just like what we have done in tut1)
  2. Quick overview of linked list
  3. A very simple way of enabling a user to add multiple objects

First: Adding Different Shapes

Like in tut1, we will create a new shape... we are working on a library called SVG rendering library.

Just type svg in Manage nuget and it will appear as first result:

  1. First, open http://editor.method.ac/ or any graphics program of your desire
  2. From shape library... draw any shape (I drew a heart !!)
  3. Then save your artwork (in this website file>>save image will export in a svg format)

  4. Open your downloaded svg into Notepad.
  5. Notice the path tag it will contain a d property... copy what is inside.

  6. Create a class that inherits from the base class shapes.
  7. Add the draw_svg virtual function as discussed in tut1.
  8. Put the copied d property from the Notepad into a string variable

The class at the end should be something like this:

class heart : shapes
    {
        private string sData = "m114.071045,61.254959c40.765015,-116.94968 200.484436,
                    0 0,150.36232c-200.484055,-150.36232 -40.763611,-267.312 0,-150.36232z";

        private Region region;
        public override Svg.SvgPath draw_svg()
        {
            Svg.SvgPath pa = new Svg.SvgPath();
            Svg.Pathing.SvgPathSegmentList svgSvgPathSegmentList = new Svg.Pathing.SvgPathSegmentList();
            var converter = TypeDescriptor.GetConverter(typeof(Svg.Pathing.SvgPathSegmentList));
            pa.PathData = (Svg.Pathing.SvgPathSegmentList)converter.ConvertFrom(sData);

            Svg.ISvgRenderer render = null;

            region = new Region(pa.Path(render));
            return pa;
        }
    }

Second: Quick Overview of Linked List

Linked list is just like a chain of objects tied together. Each object of this chain (named node) points to the other object... by this way, one can add multiple objects to a list without defining a specific size of the list from the beginning... this will your user draw numerous objects.

To define a list:

[__b__]List<int> list = new List<int>();

or for an object of a class
List<class> list = new List<class>();

To add a new item at the end:

list.Add(class_object);

To insert at a specified index:

list.Insert(index, class_object);

To remove:

list.RemoveAt(index);

To select specific object at specific index:

class_name a= list.ElementAt<class_name>(index);

Third: Working On Our Project

First things first, we are working on form.cs:

  1. Let's define a private data member named shape_list:
    List<shapes> shape_list = new List<shapes>();

    Now to make our program interactive, we would need to capture a key from the key either for example "s" star and "h" for heart. This will modify an action string that we will need to identify which object to be drawn.

    Then, we will need to capture the mouse click to the desired shape to the desired location.

  2. So, let's begin with defining the action string as a private data member:
    string action = "";
  3. Define methods to capture the mouse click and the keybaord:

    From the designer of the form >>>properties >>>events >>> under action >>double click mouse click area.

    This will create private void Form1_MouseClick(object sender, MouseEventArgs e) in back code.

    From the designer of the form>>>properties >>> events >>> under key >>double click key down.

    This will create private void Form1_KeyDown(object sender, KeyEventArgs e) in back code.

    In keydown method, write:

    private void Form1_KeyDown(object sender, KeyEventArgs e)
            {
                if (e.KeyData == Keys.S)
                {
                    action = "star";
                }
                else if (e.KeyData == Keys.H)
                {
                    action = "heart";
                }
            }

    Now, in mouse click method, write two conditions to handle both actions:

    private void Form1_MouseClick(object sender, MouseEventArgs e)
          {
              if (action.Equals("star"))
              {
                  shapes sh = new shapes(); //define base class
                  star st = new star(); //define derived class
                  sh = st; //put derived inside base
    
                  sh.translateX = e.X;//capture location of mouse in x axis
                  sh.translateY = e.Y;//capture location of mouse in y axis
                      //and put them into translateX and translateY variables
                                      //in shape variable
                      //in shapes base class
    
                  shape_list.Add(st);// add this shape to the shape_list
                  Invalidate();//call the on paint function
              }
    
    
              else if (action.Equals("heart"))
              {
                  shapes sh = new shapes();  //define base class
                  heart h = new heart();     //define derived class
                  sh = h;                    //put derived inside base
    
                  sh.translateX = e.X;       //capture location of mouse in x axis
                  sh.translateY = e.Y;       //capture location of mouse in y axis
                             //and put them into translateX and translateY variables
                                             //in shape variable
                                 // in shapes base class
    
                  shape_list.Add(h);         // add this shape to the shape_list
                  Invalidate();              //call the on paint function
              }
          }
    
  4. But the derived classes don't yet understand the translateX and translateY... as drawSvg depends on the derived class, so we must modify the draw_svg in derived class (don't forget that draw svg is a way to use the path data defined into the svg file into a GraphicsPath to be drawn into your Windows Form).

    So, we will have to modify our old star class to be:

    class star :shapes
       {
           private string sData = "m1.212486,47.649818l47.846649,0l14.78479,
                                  -45.453926l14.784615,45.453926l47.846909,0l-38.708832,
                                  28.091873l14.785934,45.454201l-38.708626,-28.09272l-38.708832,
                                  28.09272l14.785782,-45.454201l-38.708389,-28.091873l0,0z";
       // i just modified the sData to make the star smaller and to be more precise
    
           private Region region;
           public override Svg.SvgPath draw_svg()
           {
               Svg.SvgPath pa = new Svg.SvgPath();
               Svg.Pathing.SvgPathSegmentList svgSvgPathSegmentList =
                                              new Svg.Pathing.SvgPathSegmentList();
               var converter = TypeDescriptor.GetConverter(typeof(Svg.Pathing.SvgPathSegmentList));
               pa.PathData = (Svg.Pathing.SvgPathSegmentList)converter.ConvertFrom(sData);
    
    
               Svg.ISvgRenderer render = null;
    
           //---------modification
               GraphicsPath alu = new GraphicsPath();//define a Graphics path
               alu = pa.Path(render);// way to connect between graphics path and svg path
                     // as this code can only apply translate on graphics path
                     // and we need to draw the defined svg
                     // so we need to make a connection between
                                     // GraphicsPath and svgPath
    
               Matrix m = new Matrix();//define matrix to apply translation
    
           //apply translation using the translateX and translateY defined in
               //base class on the MATRIX
           //we still need to apply this translation on graphics path itself
    
               m.Translate(translateX, translateY, MatrixOrder.Append);
               alu.Transform(m);//apply translation on graphics path itself
    
               region = new Region(pa.Path(render));//svg library needs a Renderer to convert vectors
           //---------modification
    
               return pa;
           }
       }
    

    The same would be done on heart derived class:

    class heart : shapes
        {
            private string sData = "m114.071045,61.254959c40.765015,-116.94968 200.484436,
                                    0 0,150.36232c-200.484055,-150.36232 -40.763611,
                                    -267.312 0,-150.36232z";
    
            private Region region;
            public override Svg.SvgPath draw_svg()
            {
                Svg.SvgPath pa = new Svg.SvgPath();
                Svg.Pathing.SvgPathSegmentList svgSvgPathSegmentList = 
                                                new Svg.Pathing.SvgPathSegmentList();
                var converter = TypeDescriptor.GetConverter(typeof(Svg.Pathing.SvgPathSegmentList));
                pa.PathData = (Svg.Pathing.SvgPathSegmentList)converter.ConvertFrom(sData);
    
                Svg.ISvgRenderer render = null;
    
                GraphicsPath alu = new GraphicsPath();
                alu = pa.Path(render);
                Matrix m = new Matrix();
                m.Translate(translateX, translateY, MatrixOrder.Append);
                alu.Transform(m);
                region = new Region(pa.Path(render));
    
                return pa;
            }
        }
  5. Now, we only need to change the onpaint function inside the form.cs:

    By just making a simple foreach loop (just like a normal for loop, but won't require an index or a stopping condition... it just loops on items inside a list):

    protected override void OnPaint(PaintEventArgs e)
            {
                Graphics g = e.Graphics;
    
                foreach (shapes sh in shape_list)//just loop on each item of the list 
                                                 //and name this item sh
                {
                    e.Graphics.DrawPath(new Pen(Brushes.Black, 2), sh.draw_svg().Path(render));
                    e.Graphics.FillPath(Brushes.Khaki, sh.draw_svg().Path(render));
                }
            }

Now, when you run your program, you would:

  • if you want to draw star, press s on keyboard to draw star, then click on mouse on the position to draw star
  • if you want to draw heart, press h on keyboard to draw heart, then click on mouse on the position to draw heart

It is very important for me to know what you think of these tutorials... are they up to what you have expected ?? ... It is really important for me to get your feedback.

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