[教程 7] 在线段中拖放点






4.68/5 (8投票s)
在本教程中,我们将创建一个技术,允许用户通过在线条点周围绘制圆圈来控制线条内的点。当用户点击一个圆圈时,他将控制相应的点。
引言
这是关于如何使用 C# 在 Windows 窗体中构建图形程序并将艺术品导出为矢量格式的系列教程的第七篇。
- 教程1---https://codeproject.org.cn/Tips/1082353/tut-GDIplus-Artwork-from-svg
- 教程2---https://codeproject.org.cn/Tips/1082633/interactivaly-add-multiple-shapes-using-linked-lis
- 教程3---https://codeproject.org.cn/Tips/1084073/tut-graphics-program-using-Csharp-Drag-Drop-Delete
- 教程4---https://codeproject.org.cn/Tips/1105143/tut-Draw-Lines-with-Circle-and-Rectangle-End
- 教程5---https://codeproject.org.cn/Tips/1105495/tut-form-communication-multi-tab-program-custom-cu
- 教程6--https://codeproject.org.cn/Tips/1106645/tut-Line-Drag-Drop-Line-Selection
……此外,您还将了解如何移动、删除、Ctrl+Z您的矢量图,并将其保存为特殊格式,以便您的程序再次读取。
此外,我们将学习如何保存 XML 文件... 如何导出为 Verilog 格式... 如何使用星形算法... 如何使用手形工具... 如何手动创建撤销技术。
您将能够构建什么
- https://drive.google.com/folderview?id=0B739QmcCMLMHVU1DbHhIQlZ3MTA&usp=sharing
- https://www.youtube.com/watch?v=6hXJ1EoAYc0
在本教程中,我们将创建一个技术,允许用户通过在线条点周围绘制圆圈来控制线条内的点。当用户点击一个圆圈时,他将控制相应的点。
背景
- 教程1---https://codeproject.org.cn/Tips/1082353/tut-GDIplus-Artwork-from-svg
- 教程2---https://codeproject.org.cn/Tips/1082633/interactivaly-add-multiple-shapes-using-linked-lis
- 教程3---https://codeproject.org.cn/Tips/1084073/tut-graphics-program-using-Csharp-Drag-Drop-Delete
- 教程4---https://codeproject.org.cn/Tips/1105143/tut-Draw-Lines-with-Circle-and-Rectangle-End
- 教程5---https://codeproject.org.cn/Tips/1105495/tut-form-communication-multi-tab-program-custom-cu
- 教程6--https://codeproject.org.cn/Tips/1106645/tut-Line-Drag-Drop-Line-Selection
教程地图
- 编辑
super_form[desgin]
以添加新工具的图标,我们称之为point_mover_tool
。 - 然后编辑操作枚举,编辑
super_form.cs
以处理图标的点击功能和该工具的关键字,该工具的关键字将是'P'
。 - 编辑
lines.cs
类以创建一种能够查看点的技术。 - 现在我们将处理
Form.cs
,编辑Form.cs
中的OnPaint
以实际查看点,这将在点击point_mover_tool
时发生在Form1_MouseClick
中。 - 继续
Form.cs
中的移动功能Form1_MouseMove
以实际移动点。 - 当所有线条都移动时更新点 (a) 编辑 lines.cs 类 (b) 使用 Form.cs 中的 Form1_MouseMove 的移动所有线条部分。
1-编辑super_form[design]
以添加新工具的图标,我们称之为point_mover_tool
。
使用super_form[design]
,添加一个按钮。
将按钮的图标设置为此图片
只需将名称更改为point_tool_button
结果将是
2-编辑 action 枚举,然后编辑 super_form.cs
以处理图标的点击功能和该工具的关键字,该工具的关键字将是 'P'
。
编辑动作枚举,为新工具添加一个新动作,我们称之为point
动作
public enum action
{
star, heart, line, move,point, none
}
然后为新添加的按钮创建一个点击功能
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
}
我们将为这个工具添加一个自定义光标,使用这个光标
并按照本教程中的讨论添加
并将点击功能编辑为
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;
}
此外,别忘了编辑tabControl1_KeyDown
,为这个新工具添加一个新关键字,它将简单地包含与点工具按钮功能中相同的代码。
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-编辑 lines.cs
类以创建一种能够查看点的技术
移动点的概念需要用户在所需移动点上看到一个可见的形状,这将使用户能够移动这个形状以移动点。
所以我们用圆圈作为点周围的形状,把圆圈放到一个列表中
public List<GraphicsPath> circle_points_list = new List<GraphicsPath>();
我们用一个int
来指示哪个点被选中,并将其初始化为-1
public int selected_point = -1;
因此,当线条绘制完成时,我们需要在所有点周围绘制这个形状,所以我们将处理这个函数 draw_arrow()当线条绘制完成时调用了它
然后,在 draw_arrow() 函数内部调用一个新函数,该函数将在所有点周围创建圆圈。
public void draw_arrow()
{
.
.//old code
.
//new code
create_circles_around_points();
//new code
.
. //old code
.
}
现在我们来创建这个函数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-现在我们将处理Form.cs
,编辑Form.cs
中的OnPaint
以实际查看点,这将在点击point_mover_tool
时发生在Form1_MouseClick
中。
现在我们回到Form.cs
的工作,我们将从用户完成线条绘制后以及当他点击point_mover_tool
时查看点开始。
所以我们将开始处理OnPaint
函数,仅仅是为了查看点周围的圆圈,以告诉用户在哪里点击来控制所需的点。
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
}
然后我们将使用Form1_MouseClick
,以便用户可以点击他需要移动的所需点。
为了避免从两条线中选择两个点,我们将使用一个整数来指示哪条线被用来控制其点。
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
}
现在,在设置行对象中的选定点变量后,我们需要以不同的方式查看选定点,所以让我们使选定点具有实心颜色。
因此,我们再次编辑OnPaint
函数,以不同的方式(具有实心背景)显示选定点。
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-继续使用Form.cs
中的移动函数Form1_MouseMove
来实际移动点。
现在我们来处理真正移动点的函数,所以我们将在Form1_MouseMove
上工作,我们将为新动作创建一个新条件。
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
}
首先,我们必须区分point_mover_tool
的两种用法,
- 用户第一次点击点进行移动时----> 查看用户选择了哪个点
- 一旦用户选择了要移动的点,就不需要循环查找用户需要移动哪个点,因为他仍然按住该点 ----> **移动点本身**
所以我们会使用一个名为continous_select
的布尔值(之前在move_tool中使用过),但是用户要么使用move_tool
,要么使用point_mover_tool
,所以我们只会为这两个工具使用一个布尔值。
第一次用户选择所需点时,我们会进行操作(条件是continous_select=false
),这里我们会使用之前在Form1_MouseClick
中使用的相同代码来知道哪个点被选中。
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
}
现在我们将处理第二个条件,即当用户按住该点时,我们移动点本身。
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
}
- 当所有线条都移动时更新点 (a) 编辑 lines.cs 类 (b) 使用 Form.cs 中的 Form1_MouseMove 的移动所有线条部分。
6-当所有线条都移动时更新点
别忘了我们还没有解决线条移动时更新点的问题,在之前的教程中,我们只关注移动时线条的显示,而没有真正移动线条的点。
所以我们会从两个方面着手
(a) 使用Form.cs
中的Form1_MouseMove
来处理移动整条线的部分
(b) 编辑 lines.cs
类
(a) Form1_MouseMove
我们将编辑Form1_MouseMove
中移动操作的部分,我们将使用
PointF[]=GraphicsPath.PathPoints
但这会返回 PointF 数组,所以我们将在 Lines.cs
类中创建一个 PointF 数组。
在 lines.cs
中
public PointF[] point_lineF;
在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) 在 lines.cs
类中创建 update_circles_around_points
此函数将从我们刚刚在lines.cs
中创建的pointf_array更新圆圈列表和点列表(普通点而非pointf)。
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);
}
}