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
- 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 a d
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 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:
- 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();
star st = new star();
sh = st;
sh.translateX = e.X;
sh.translateY = e.Y;
shape_list.Add(st);
Invalidate();
}
else if (action.Equals("heart"))
{
shapes sh = new shapes();
heart h = new heart();
sh = h;
sh.translateX = e.X;
sh.translateY = e.Y;
shape_list.Add(h);
Invalidate();
}
}
- 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";
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;
}
}
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 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)
{
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.