Interactively Add Multiple Shapes Using Linked Lists GDI+ SVG





4.00/5 (3 votes)
Tutorial 2 (how to interactively add multiple shapes GDI using linked lists)
- Download source code for this tutorial - 1.3 MB
- What you would be able to build in the future - 1.9 MB
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:
- https://drive.google.com/folderview?id=0B739QmcCMLMHVU1DbHhIQlZ3MTA&usp=sharing
- https://www.youtube.com/watch?v=6hXJ1EoAYc0
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
- Adding different shapes (just like what we have done in tut1)
- Quick overview of linked list
- 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:
- First, open http://editor.method.ac/ or any graphics program of your desire
- From shape library... draw any shape (I drew a heart !!)
- Then save your artwork (in this website file>>save image will export in a svg format)
- Open your downloaded svg into Notepad.
- Notice the
path
tag it will contain ad
property... copy what is inside. - Create a class that inherits from the base class shapes.
- Add the
draw_svg virtual
function as discussed in tut1. - Put the copied
d
property from the Notepad into astring
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:
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:
- 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.
- So, let's begin with defining the action string as a private data member:
string action = "";
- 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 } }
- But the derived classes don't yet understand the translateX and translateY... as
drawSvg
depends on the derived class, so we must modify thedraw_svg
in derived class (don't forget that draw svg is a way to use the path data defined into the svg file into aGraphicsPath
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; } }
- Now, we only need to change the
onpaint
function inside the form.cs:By just making a simple
foreach
loop (just like a normalfor
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.