[tut 7] Drag & Drop points in lines [more…]
Introduction
This is the Seventh 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.
- tut1—http://www.codeproject.com/Tips/1082353/tut-GDIplus-Artwork-from-svg
- tut2—http://www.codeproject.com/Tips/1082633/interactivaly-add-multiple-shapes-using-linked-lis
- tut3—http://www.codeproject.com/Tips/1084073/tut-graphics-program-using-Csharp-Drag-Drop-Delete
- tut4—http://www.codeproject.com/Tips/1105143/tut-Draw-Lines-with-Circle-and-Rectangle-End
- tut5—http://www.codeproject.com/Tips/1105495/tut-form-communication-multi-tab-program-custom-cu
- tut6–http://www.codeproject.com/Tips/1106645/tut-Line-Drag-Drop-Line-Selection
…. 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:
- https://drive.google.com/folderview?id=0B739QmcCMLMHVU1DbHhIQlZ3MTA&usp=sharing
- https://www.youtube.com/watch?v=6hXJ1EoAYc0
in this tut we will create a technique to enable the user to control points within a drawn line, by drawing circles around points of the line , when the user clicks a circle he would control the corresponding point.
Background
- tut1—http://www.codeproject.com/Tips/1082353/tut-GDIplus-Artwork-from-svg
- tut2—http://www.codeproject.com/Tips/1082633/interactivaly-add-multiple-shapes-using-linked-lis
- tut3—http://www.codeproject.com/Tips/1084073/tut-graphics-program-using-Csharp-Drag-Drop-Delete
- tut4—http://www.codeproject.com/Tips/1105143/tut-Draw-Lines-with-Circle-and-Rectangle-End
- tut5—http://www.codeproject.com/Tips/1105495/tut-form-communication-multi-tab-program-custom-cu
- tut6–http://www.codeproject.com/Tips/1106645/tut-Line-Drag-Drop-Line-Selection
Map of the tut
- edit the
super_form[desgin]
to add an icon for the new tool, we would call itpoint_mover_tool
. - edit action enum then, edit the
super_form.cs
to work with the click function of the icon and the keyword of that tool , the keyword for this tool would be'P'
. - edit the
lines.cs
class to create a technique to be able to see the points - now we would work with the
Form.cs
edit theOnPaint
inside theForm.cs
to actually view the points, this would occur when clicking thepoint_mover_tool
, insideForm1_MouseClick
- continue with the
Form.cs
with the function of the moveForm1_MouseMove
to actually move the points. - update the points when all the line is moved through (a) editing lines.cs class (b) work with the
Form.cs
with theForm1_MouseMove
with the section of moving all the line
1-edit the super_form[desgin]
to add an icon for the new tool , we would call itpoint_mover_tool
.
work with the super_form[desgin]
, add a button.
set the icon of the button to this image
just change the name to point_tool_button
so the result would be
2-edit action enum then, edit the super_form.cs
to work with the click function of the icon and the keyword of that tool , the keyword for this tool would be 'P'
.
edit the action enum to have a new action for the new tool, we would call it point
action
public enum action
{
star, heart, line, move,point, none
}
then create a click function for the newly added button
private void point_tool_button_Click(object sender, EventArgs e)
{
super_action = action.point;
toolStrip2.Visible = false;//make the tool of the line proprieties
// invisible when clicking this button
}
we would add a custom cursor for this tool , use this cursor
and add like discussed in this tut
and edit the click function to be
private void point_tool_button_Click(object sender, EventArgs e)
{
super_action = action.point;
cursor_super = new Cursor(drag_and_drop_and_delete.Properties.Resources.point__cursor.Handle);
this.Cursor = cursor_super;
toolStrip2.Visible = false;
}
also don’t forget to edit the tabControl1_KeyDown
to add a new keyword for this new tool , it would simply contain the same code like in the function for the button of the point tool.
private void tabControl1_KeyDown(object sender, KeyEventArgs e)
{
.//old code
.
.
//new code
else if (e.KeyData == Keys.P)
{
super_action = action.point;
cursor_super = new Cursor(drag_and_drop_and_delete.Properties.Resources.point__cursor.Handle);
this.Cursor = cursor_super;
toolStrip2.Visible = false;
}
//new code
.
.
.//old code
}
3-edit the lines.cs
class to create a technique to be able to see the points
the concept of moving the points would require a shape visible to the user on the required point to move ,which would enable the user to move this shape in order to move the point.
so lets use circles as the shape around th points, lets put the circles inside a list
public List<GraphicsPath> circle_points_list = new List<GraphicsPath>();
lets use an int
to tell which point is selected, and lets initialize it with -1
public int selected_point = -1;
so we require to draw this shape around all points, when the line is done drawing , so we would work with the function of draw_arrow()
which was called when the line is finished drawing
then call inside the draw_arrow() function a new function that would create circles around all points
public void draw_arrow()
{
.
.//old code
.
//new code
create_circles_around_points();
//new code
.
. //old code
.
}
now lets create this function create_circles_around_points()
public void create_circles_around_points()
{
circle_points_list.Clear();//begin with clearing all the circle list
foreach (Point p in point_line)//iterate through all points in the lines class
{
GraphicsPath circle = new GraphicsPath();
circle.AddEllipse(p.X-10, p.Y-10, 20, 20);//create circle around each point
circle_points_list.Add(circle);//and add it to circle list
}
}
4-now we would work with the Form.cs
edit the OnPaint
inside the Form.cs
to actually view the points, this would occur when clicking the point_mover_tool
, inside Form1_MouseClick
now lets return back to working with Form.cs
, we would start with viewing the points after the user finishes drawing the line and when he clicks the point_mover_tool
.
so we would start working with OnPaint
function, to just view the circles around the points , to tell the user where to click to control the required point .
protected override void OnPaint(PaintEventArgs e)
{
.//old code
.
foreach (lines l in lines_list)
{
.//old code
.
//new code
if (super_form.super_action.Equals(action.point))
{
foreach (GraphicsPath circle in l.circle_points_list)
{
Pen selection_pen_line = new Pen(Brushes.Chocolate, 2);
g.DrawPath(selection_pen_line, circle);
}
}
//new code
.
.//old code
}
.
.//old code
}
then we would work with Form1_MouseClick
, to enable the user to click the required points that he needs to move.
to avoid selecting 2 points from 2 lines we would use an integer to tell which line is used to control its points.
public int selected_point_in_line=0;
private void Form1_MouseClick(object sender, MouseEventArgs e)
{
. //old code
.
//new code
else if (super_form.super_action.Equals(action.point))
{
if (e.Button == m)//enable the if condition when the user clicks on the left
//button of the mouse
{
//know which point is selected
Point with_offset = new Point(e.X - 10, e.Y - 10);//offset because of using
//a custom cursor
int counter_for_lines = 0;
foreach (lines line in lines_list)
{
int count = 0;
foreach (GraphicsPath circle in line.circle_points_list)
{
if (circle.IsVisible(with_offset))
{
line.selected_point = count;//set the selected_point variable
//inside the line object to the count
//of the selected point
selected_point_in_line = counter_for_lines;
Invalidate();
break;//once found you don't need to continue looping through all points
}
count++;
}
counter_for_lines++;
}
}
}
//new code
.
.//old cod
}
now after setting the selected point variable inside the line object , we need to view the selected point in a different way , so lets make the selected point having a solid color
so lets edit the OnPaint
function again , to view the selected point in a different way of having a solid background
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
int counter_line = 0;
foreach (lines l in lines_list)
{
.
. //old code
.
//new point
if (super_form.super_action.Equals(action.point))
{
foreach (GraphicsPath circle in l.circle_points_list)
{
int count = 0;
if (count == l.selected_point && counter_line == selected_point_in_line)
{
g.FillPath(Brushes.Chocolate,circle);
}
else
{
Pen selection_pen_line = new Pen(Brushes.Chocolate, 2);
g.DrawPath(selection_pen_line, circle);
}
count++;
}
}
counter_line++;
//new point
}
.
. //old code
.
}
5-continue with the Form.cs
with the function of the move Form1_MouseMove
to actually move the points.
now lets work on the function that would really move the points, so we would work on the Form1_MouseMove ,
we would creae a new codition for the new action
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (super_form.super_action.Equals(action.move))
{
.
.//old code
.
}
//new code
else if (super_form.super_action.Equals(action.point))
{
}
//new code
}
first we must differ between 2 uses of the point_mover_tool
,
- the first time the user clicks the point to move—-> look which point the user selects
- once the user selected the point to move , no need to loop to find which point the user needs to move , as he is still holding the point —-> move the point itself
so we would use a bool called continous_select
(was used before in the move_tool) but the user either usesmove_tool
or the point_mover_tool
, so we would use only one bool for both of the two tools
we would work the first time the user selects the required point , ( condition with continous_select=false
) here we would use the same code that was used before in Form1_MouseClick
to know which point is selected
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (super_form.super_action.Equals(action.move))
{
.
.//old code
.
}
//new code
else if (super_form.super_action.Equals(action.point))
{
if(e.Button == m) //only work when left_mouse_click is selected
{
//know which point is selected
Point with_offset = new Point(e.X - 10, e.Y - 10);
int counter_for_lines = 0;
foreach (lines line in lines_list)
{
int count = 0;
foreach (GraphicsPath circle in line.circle_points_list)
{
if (!continous_select)//first condition when the user clicks the
// point for the first point
{
if (circle.IsVisible(with_offset))
{
line.selected_point = count;
selected_line = counter_for_lines;
continous_select = true;//here make the user hold the point
selected_point_in_line = counter_for_lines;
break;// no need to continue looping
}
}
count++;
}
counter_for_lines++;
}
if (continous_select)
{
//movement itself
}
}
else//when the user doesn't click the left_mouse_click
{
continous_select = false;//reset the holding bool
}
Invalidate();//redraw the form
}
//new code
}
now we would work on the second condition , that of when the user holds the point so we work on moving the points themselves
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (super_form.super_action.Equals(action.move))
{
.
.//old code
.
}
//new code
else if (super_form.super_action.Equals(action.point))
{
if(e.Button == m)
{
//know which point is selected
Point with_offset = new Point(e.X - 10, e.Y - 10);
int counter_for_lines = 0;
foreach (lines line in lines_list)
{
int count = 0;
foreach (GraphicsPath circle in line.circle_points_list)
{
if (!continous_select)
{
if (circle.IsVisible(with_offset))
{
line.selected_point = count;
selected_line = counter_for_lines;
continous_select = true;
selected_point_in_line = counter_for_lines;
break;
}
}
count++;
}
counter_for_lines++;
}
if (continous_select)
{
//actually move point
lines selected_line_for_point = lines_list[selected_line];
//get the selected line from the list
int selected_point = selected_line_for_point.selected_point;
//get index of the selected point
selected_line_for_point.point_line[selected_point] = with_offset;
//use the index of the selected point and update that point with the position
//of the mouse
selected_line_for_point.path_line = new GraphicsPath(); //reset the whole line
selected_line_for_point.path_line.AddLines(selected_line_for_point.point_line.ToArray());
//create the line with the updated points
//move the circles
GraphicsPath circle_g = new GraphicsPath();
circle_g.AddEllipse(with_offset.X - 10, with_offset.Y - 10, 20, 20);
//create a new circle with a new position
selected_line_for_point.circle_points_list[selected_point] = circle_g;
//update the circle list with the new circle , using the index of the
//selected point
//update outline and arrow
selected_line_for_point.draw_arrow();
}
}
else
{
continous_select = false;
}
Invalidate();
}
//new point
}
- update the points when all the line is moved through (a) editing lines.cs class (b) work with the
Form.cs
with theForm1_MouseMove
with the section of moving all the line
6-update the points when all the line is moved
don’t forget that we haven’t yet solved the condition of updating the points when the line is moved, in the previous tuts we were only concerned with viewing the line when moving without really moving the line’s points
so we would work on two points
(a) work with the Form.cs
with the Form1_MouseMove
with the section of moving all the line
(b) editing lines.cs
class
(a) Form1_MouseMove
we would edit the section of the move action inside the Form1_MouseMove
, we would use
PointF[]=GraphicsPath.PathPoints
but this would return array of PointF , so we would create in Lines.cs
class a poinf array
in lines.cs
public PointF[] point_lineF;
in Form1_MouseMove
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (super_form.super_action.Equals(action.move))
{
skip_checking_lines = false;
if (first && e.Button == m)
{
if (!skip_checking_lines)
{
.
.//old code
.
}
if (is_selected && e.Button == m)
{
selected_recatngle.Location = e.Location;
if (selected_type.Equals("shape"))
{
.
.//old code
.
}
if (selected_type.Equals("line"))
{
.
.//old code
.
//new code
selected_line_from_list.point_lineF = selected_line_from_list.path_line.PathPoints;
selected_line_from_list.update_circles_around_points();
//we would create this function in the lines.cs
// this function would update the circle list and the point list
// (of normal points not the pointf)
//from the pointf_array that we have just created in the lines.cs
//new code
}
}
else
{
first = true;
continous_select = false;
}
Invalidate();
oldX = e.X;
oldY = e.Y;
}
else if (super_form.super_action.Equals(action.point))
{
.
.//old code
.
}
}
(b) create the update_circles_around_points
in the lines.cs
class
this function would update the circle list and the point list (of normal points not the pointf) from the pointf_array that we have just created in the lines.cs
public void update_circles_around_points()
{
circle_points_list.Clear();//clear the circles point list
foreach (PointF p in point_lineF)//update the circles point list
{
GraphicsPath circle = new GraphicsPath();
circle.AddEllipse(p.X - 10, p.Y - 10, 20, 20);
circle_points_list.Add(circle);
}
for (int i = 0; i < point_lineF.Count(); i++)//update the point list
{
point_line[i] = new Point((int)point_lineF[i].X, (int)point_lineF[i].Y);
}
}