Introduction
This is the Sixth tutorial of a series of tutorials of how to build a graphics program using C# in Windows form that exports the artwork in a vector format.
.... also, you would understand how to move, delete, ctrl z your vector art and save it in a special format to be read by your program again.
Also, we will learn how to save XML files... how to export in verilog format... how to use a star algorithm... how to use hand tool... how to manually create the ctrl z technique.
What you will be capable of building:
we will build an outline line around the drawn lines (once the user draws the line arrow; finishes line drawing), so we would test the mouse click on this outline to know which line is selected.
Update
built a select function inside the move tool so that the user would select the objects and lines without the need for the user to move the objects and lines
Background
Map of the tut
outline line
- edit the
line class
(to add the outline line)
- edit the
move function
to determine selected line
- edit the
move function
to move the line and te outline
- edit
onPaint()
function to draw the line and the outline
- only make outline visible when choosing the move tool
add the selection function to the move tool
1-work with the Form1_MouseClick in Form1 function inside the if of move tool and copy both the technique of knowing which is selected
2- Deselect Function
3-Editing the delete condition tabControl1_KeyDown in the super_form
outline line
1-edit the line class
(to add the outline line)
we would edit the line class to have the outline (the outline would be a graphics path),
public GraphicsPath outline = new GraphicsPath();
and we would create this outline once the user end drawing his line (from previous tuts we have created a function that is called once the user finishes drawing, this was the draw_arrow()
function),
so we would edit the draw_arrow()
the outline would be a copy of the line itself , we would use the clone function
, (we would use clone function , due to the concept of C# itself) in C# every object is a pointer, so when you copy a pointer (called b) to a pointer (called a),
object b;
object a = b;
pointer a is not really an independent pointer from b, so changes in object a won't be independent from object b. So we would use the Clone()
function , but we must define the type of the object itself
object b;
object a = (object) b.Clone();
Now in our code
outline =(GraphicsPath) path_line.Clone();
Then we need to use the Widen()
function , which is the function that creates a stroke around the line (to serve the concept of line selection)
and the Widen()
function needs a pen object, ,which simply creates a pen with a color and a width for this pen, to be the path that is created from the Widen()
function.
Pen pen = new Pen(Brushes.Red, 10);
outline.Widen(pen);
so to sum things up the line function would be
public GraphicsPath outline = new GraphicsPath();
public void draw_arrow()
{
.
.
outline =(GraphicsPath) path_line.Clone();
Pen pen = new Pen(Brushes.Red, 10);
outline.Widen(pen);
.
.
}
2-edit the move function
to determine selected line
from the previous tutorials we worked inside the move function
, before moving the objects we first worked on knowing which object the user selected, we would do something similar to know which line is selected, but if the user selected an object not a line , then there wouldn't be a need to test lines , so we would use a bool variable know if an object is selected so no need to test the line
public bool skip_checking_lines = false;
and we would use a string to tell which type is selected
public string selected_type = "";
and inside the move function,
the BOLD
is the new code added to the move function
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
skip_checking_lines = false;
if (first == true && e.Button == m && super_form.action.Equals("move"))
{
int counter = 0;
foreach (shapes sh in shape_list)
{
if (sh.draw_svg().Path(render).IsVisible(e.Location))
{
selected_recatngle = sh.draw_svg().Path(render).GetBounds();
selected = counter;
is_selected = true;
skip_checking_lines = true;
selected_type = "shape";
break;
}
else
{
is_selected = false;
}
counter++;
}
if (!skip_checking_lines)
{
}
}
.
.
.
}
now we would work inside the section of knowing which line is selected.
First we would use an integer to hold the selected line
public int selected_line;
here we would write our code within if (!skip_checking_lines)
to know which line is selected, just add a foreach
loop to loop on all line list,
if (!skip_checking_lines)
{
int line_move_counter = 0;
foreach (lines Line in lines_list)
{
line_move_counter ++;
}
}
then put an if condition to test if the mouse click is inside the outline
- use the
IsVisible
function
- let the selected type be "line"
- if the test succeeded let the
is_selected
bool be true
- if it fails
is_selected
bool be true
- if the test succeeded stop further testing using
break
, but before this use the an integer selected_line
to hold the line_move_counter
if (!skip_checking_lines)
{
int line_move_counter = 0;
foreach (lines Line in lines_list)
{
if (Line.outline.IsVisible(e.Location) ||
Line.arrow_path_line.IsVisible(e.Location))
{
selected_type = "line";
is_selected = true;
continous_select = true; selected_line = line_move_counter;
break;
}
else
{
is_selected = false;
}
line_move_counter++;
}
}
There is an important concept we need to discuss
no need to test if the mouse to see of it is inside the outline or not , once you tested First time, I mean you only need to test once and then the user would continue moving the line , so no need to continually test if the mouse inside the outline after you tested it first.
so we would use a bool variable called continous_select
and initialize it to false
public bool continous_select = false;
so now when using this concept, the code would be
if (!skip_checking_lines)
{
int line_move_counter = 0;
Point with_offset = new Point(e.X - 10, e.Y - 10); foreach (lines Line in lines_list)
{
if (continous_select == true)
{
selected_type = "line";
is_selected = true;
}
else if (continous_select != true
&&( Line.outline.IsVisible(with_offset) ||
Line.arrow_path_line.IsVisible(with_offset)))
{
selected_type = "line";
is_selected = true;
continous_select = true;
selected_line = line_move_counter;
break;
}
else
{
is_selected = false;
}
line_move_counter++;
}
}
but we still need to reset the continous_select
bool
the general concept of the mouse_move ,
we would put reset in the last else
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (first == true && e.Button == m && super_form.action.Equals("move"))
{
}
if (is_selected == true && e.Button == m && super_form.action.Equals("move") )
{
}
else
{
continous_select = false;
}
}
you could have seen that i have used an offset point for testing , we just used an offset of -10 for e.X and -10 for e.Y
Point with_offset = new Point(e.X - 10, e.Y - 10);
this is because from concept of using a custom cursor with a default center Hotspot.
so we need to change the hotspot by a simple offset
3-edit the move function
to move the line and te outline
now we would edit the move_function
to move the lines, we would edit the section under the if (is_selected == true && e.Button == m && super_form.action.Equals("move") )
that works with moving objects and moving lines , but before this we would use 2 integers for moving
public int oldX, oldY;
so our code would be , don't forget to update the oldx and oldy , we would use a Matrix to move all of
- the line list itself
- arrow
- outline
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (first == true && e.Button == m && super_form.action.Equals("move"))
{
}
if (is_selected == true && e.Button == m && super_form.action.Equals("move") )
{
if (selected_type.Equals("shape"))
{
}
if (selected_type.Equals("line"))
{
Matrix ma = new Matrix();
ma.Translate(e.X - oldX, e.Y - oldY);
lines_list[selected_line].path_line.Transform(ma);
lines_list[selected_line].arrow_path_line.Transform(ma);
lines_list[selected_line].outline.Transform(ma);
}
}
else
{
continous_select = false;
}
Invalidate();
oldX = e.X;
oldY = e.Y;
}
4-edit onPaint()
function to draw the line and the outline
now we need to draw the outline itself for the user to know where the user would click to select the line
so edit the onPaint()
to be
foreach (lines l in lines_list)
{
Pen selection_pen_line = new Pen(Brushes.Blue, 2);
e.Graphics.DrawPath(selection_pen_line, l.outline);
e.Graphics.DrawPath(l.pen, l.path_line);
e.Graphics.FillPath(Brushes.Black, l.arrow_path_line);
}
5-only make outline visible when choosing the move tool
simply edit the onPaint()
to only draw the stroke of the lines only when the action tool is selected
foreach (lines l in lines_list)
{
if (super_form.super_action.Equals(action.move))
{
Pen selection_pen_line = new Pen(Brushes.Blue, 2);
g.DrawPath(selection_pen_line, l.outline);
}
g.DrawPath(l.pen, l.path_line);
g.FillPath(Brushes.Black, l.arrow_path_line);
}
add the selection function to the move tool
1-work with the Form1_MouseClick
in Form1
function inside the if of move tool and copy the technique of knowing which is selected
in Form1
in Form1_MouseClick
private void Form1_MouseClick(object sender, MouseEventArgs e)
{
if (super_form.super_action.Equals(action.star))
{
else if (super_form.super_action.Equals(action.heart))
{
else if (super_form.super_action.Equals(action.line))
{
else if (super_form.super_action.Equals(action.move))
{
skip_checking_lines = false;
int counter = 0;
foreach (shapes sh in shape_list)
{
if (sh.draw_svg().Path(render).IsVisible(e.Location))
{
selected_recatngle = sh.draw_svg().Path(render).GetBounds();
selected = counter;
is_selected = true;
skip_checking_lines = true;
selected_type = "shape";
break;
}
else
{
is_selected = false;
}
counter++;
}
first = false;
}
}
2- Deselect Function
here we would solve 2 points.
simply by modifying the previous Form1_MouseClick
in Form1
in hte else condition that resulted from not succeeding in selecting a shape
private void Form1_MouseClick(object sender, MouseEventArgs e)
{
if (super_form.super_action.Equals(action.star))
{ }
else if (super_form.super_action.Equals(action.heart))
{ }
else if (super_form.super_action.Equals(action.line))
{ }
else if (super_form.super_action.Equals(action.move))
{
skip_checking_lines = false;
int counter = 0;
foreach (shapes sh in shape_list)
{
if (sh.draw_svg().Path(render).IsVisible(e.Location))
{
selected_recatngle = sh.draw_svg().Path(render).GetBounds();
selected = counter;
is_selected = true;
skip_checking_lines = true;
selected_type = "shape";
break;
}
else
{
RectangleF null_rectangle = new RectangleF();
selected_recatngle = null_rectangle;
is_selected = false;
}
counter++;
}
first = false;
}
}
3-Editing the delete
condition tabControl1_KeyDown
in the super_form
edit the condition of to delete
to replace testing the selected bool with seeing selected rectangle if it is null or not
private void tabControl1_KeyDown(object sender, KeyEventArgs e)
{
.
.
.
else if (e.KeyData == Keys.Delete && selected_form.selected_recatngle!=null )
{
if (selected_form.selected_type == "shape")
{
selected_form.shape_list.RemoveAt(selected_form.selected);
RectangleF null_rectangle = new RectangleF();
selected_form.selected_recatngle = null_rectangle;
selected_form.is_selected = false;
}
selected_form.Invalidate();
}
}
If you liked the tut Vote it up
-