[教程 8] 手形工具





5.00/5 (9投票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
- 教程7---https://codeproject.org.cn/Tips/1108018/tut-Drag-Drop-points-in-lines
.... 此外,您还将了解如何移动、删除、撤销您的矢量艺术,并将其保存为特殊格式以便您的程序再次读取。
此外,我们将学习如何保存 XML 文件... 如何导出为 Verilog 格式... 如何使用星形算法... 如何使用手形工具... 如何手动创建撤销技术。
您将能够构建什么
- https://drive.google.com/folderview?id=0B739QmcCMLMHVU1DbHhIQlZ3MTA&usp=sharing
- https://www.youtube.com/watch?v=6hXJ1EoAYc0
教程地图
- 编辑
super_form
以添加手形工具图标,为此新工具添加新操作,为单击此新工具创建函数,并为此新工具使用新的自定义光标。 - 在
Form1
中,定义一个比用户可见的实际窗体大小更大的尺寸,称为AutoScrollMinSize
。 - 在
Form1
中,编辑Form1_MouseMove
并为新工具添加一个新部分,这将是该工具的核心。 - 在
Form1
中,创建Form1_MouseDown
,以完成该工具的功能。 - 在
Form1
中,编辑OnPaint
以创建绘制形状的偏移量。 - 当用户选择手形工具时按下鼠标时更改光标,这将通过
Form1
和super_form
之间的连接实现。 - 在
Form1
中,平移后释放鼠标时,将鼠标重置为打开的手形。 - 在
Form1
中,编辑之前教程中讨论过的所有先前部分,以使其与新工具良好配合。 - 启用快捷键(空格键)以使用新的手形工具。
- 在
Form1
中,使用OnPaint
来显示平移窗体时点周围的圆圈和线条轮廓。
背景
- 教程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
- 教程7---https://codeproject.org.cn/Tips/1108018/tut-Drag-Drop-points-in-lines
更新
添加了两个部分
9-启用快捷键(空格键)以使用新的手形工具。
10-在Form1
中,使用OnPaint
来显示平移窗体时点周围的圆圈和线条轮廓。
并更新了关于使用偏移量选择和移动线条和对象重要性的解释图片,因为之前的图片中发现了一个错误,但现在已修复。
1-编辑super_form
以添加手形工具图标,为此新工具添加新操作,为单击此新工具创建函数,并为此新工具使用新的自定义光标。
使用super_form[design]
,添加一个按钮。
将按钮图标设置为此图像
只需将名称更改为Hand_tool
结果将是
现在我们将使用super_form.cs
首先编辑 action 枚举以添加一个新动作
public enum action
{
star, heart, line, move,point,hand, none
}
然后我们将为新“手形”工具的新按钮添加一个单击函数。
private void Hand_tool_Click(object sender, EventArgs e)
{
super_action = action.hand;
toolStrip2.Visible = false;//make the toolbar of the line proprieties invisible when
//clicking this tool
}
现在我们需要为Hand_tool
添加一个新的自定义光标
所以像之前在这个教程中讨论的那样,将此图标添加为资源
然后编辑之前编写的Hand_tool_Click
函数,使其成为
private void Hand_tool_Click(object sender, EventArgs e)
{
super_action = action.hand;
//new code
cursor_super = new Cursor(drag_and_drop_and_delete.Properties.Resources.open_hand.Handle);
this.Cursor = cursor_super;
//new code
toolStrip2.Visible = false;
}
2-在Form1
中,定义一个比用户可见的实际窗体大小更大的尺寸,称为AutoScrollMinSize
。
public Form1()
{
InitializeComponent();
this.Size = new Size(1000, 700);
//new code
this.AutoScrollMinSize = new Size(2000, 1000);
// you must define an AutoScrollMinSize with a size bigger than that of the size of the
// Form, as it tells the actual size of the form , but not all of this space is really
// visible to the user at once, as the size(1000, 700) is the actual size visible to the
// user without need to use the hand tool, but the AutoScrollMinSize is the actual size
// the user can draw but with use of the hand tool
//new code
this.DoubleBuffered = true;
Invalidate();
}
3-在Form1
中,编辑Form1_MouseMove
并为新工具添加一个新部分,这将是该工具的核心。
我们将使用Form1
来处理手形工具本身的功能,但首先我们需要定义一些变量。
public Point prevScrollPoint;
public int pointX, pointY;
public int offsetX, offsetY;
然后让我们使用Form1_MouseMove
添加一个新条件
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
.
.//old code
.
//new code
else if (super_form.super_action.Equals(action.hand) )
{
if (e.Button == MouseButtons.Left)
//only occurs when this user clicks the left mouse click
{
}
}
//new code
}
然后在此条件中,您将使用之前编写的变量
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
.
.//old code
.
else if (super_form.super_action.Equals(action.hand) )
{
if (e.Button == MouseButtons.Left)
{
//new code
offsetX = e.X - prevScrollPoint.X;
offsetY = e.Y - prevScrollPoint.Y;
pointX = -(this.AutoScrollPosition.X);
pointY = -(this.AutoScrollPosition.Y);
pointX += -offsetX;
pointY += -offsetY;
this.AutoScrollPosition = new Point(pointX, pointY);
prevScrollPoint = e.Location;
Invalidate();
//new code
}
}
}
4-在Form1
中,创建一个Form1_MouseDown
,以完成该工具的功能。
手形工具的功能尚未完成,您必须创建一个MouseDown
函数。
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left && super_form.super_action.Equals(action.hand))
{
prevScrollPoint = e.Location;
}
}
5-在Form1
中,编辑OnPaint
以创建绘制形状的偏移量。
我们必须编辑OnPaint
以真正使用AutoScrollPosition
的值作为偏移量来转换对象。
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
//new code
g.TranslateTransform(this.AutoScrollPosition.X, this.AutoScrollPosition.Y);
//new code
.
.//old code
.
}
6-当用户选择手形工具时按下鼠标时更改光标,这将通过Form1
和super_form
之间的连接实现。
.
当鼠标被点击时,我们需要将光标更改为闭合的手形。
要做到这一点,我们必须在super_form
内部更改光标,但super_form
的光标不是静态变量,所以你必须将super_form
传递给所有Form1
元素,这会非常耗费内存,所以还有另一种我使用的方法。
即在super_form
内部使用一个静态按钮,并为该按钮分配一个点击函数,这个点击函数将简单地将super_form
的光标更改为与静态super_cursor
相同。
我们将在super_form
中工作。
根据之前的教程,我们将拥有
public static Cursor cursor_super;
今天我们将创建一个静态按钮
public static Button change_cursor_static_button;
在super_form
的构造函数中
public super_form()
{
InitializeComponent();
.
.//old code
.
//new code
change_cursor_static_button = new Button();
change_cursor_static_button.Click += change_cursor_static_button_function;
// assign a function to this button
//new code
}
那么这个函数将很简单,只是将cursor_super
分配给super_form
的光标
private void change_cursor_static_button_function(object sender, EventArgs e)
{
this.Cursor = cursor_super;
}
现在我们将与Form1
一起工作
我们将编辑Form1_MouseDown
函数
我们将添加一个新的闭合手形光标
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left && super_form.super_action.Equals(action.hand))
{
prevScrollPoint = e.Location;
Cursor new_cursor
= new Cursor(drag_and_drop_and_delete.Properties.Resources.closed_hand.Handle);
super_form.cursor_super = new_cursor;
//load this new cursor to the cursor_super of the super_cursor
super_form.change_cursor_static_button.PerformClick();
}
}
7-在Form1
中。平移后释放鼠标时,将鼠标重置为打开的手形。
简单地说,在Form1
中,我们将创建一个MouseUp函数
我们将使用打开的手形光标
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
if (super_form.super_action.Equals(action.hand))
{
Cursor new_cursor =
new Cursor(drag_and_drop_and_delete.Properties.Resources.open_hand.Handle);
super_form.cursor_super = new_cursor;
super_form.change_cursor_static_button.PerformClick();
}
}
现在手形工具的功能已经完成,但我们必须使旧教程中的旧代码与这个新工具良好配合。
8-在Form1
中,编辑我们之前在教程中讨论过的所有先前部分,以使其与新工具良好配合。
所以必须有一个偏移量
- 移动和选择线内的点,
- 这个概念也适用于点击整条线,
- 也适用于移动和选择对象
- 以及绘制对象(星形、心形)
- 以及绘制线条
1-点击线内的点
使用Form1_MouseMove
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
.
.//old code
.
else if (super_form.super_action.Equals(action.point))
{
if (e.Button == m)
{
//new code
Point with_offset = new Point(e.X - 10 - AutoScrollPosition.X,
e.Y - 10 - AutoScrollPosition.Y);
//new code
.
.//old code
.
}
.
.//old code
.
}
}
并在线中选择点
使用Form1_MouseClick
private void Form1_MouseClick(object sender, MouseEventArgs e)
{
.
.//old code
.
else if (super_form.super_action.Equals(action.point))
{
if (e.Button == m)
{
Point with_offset = new Point(e.X - 10 - AutoScrollPosition.X,
e.Y - 10 - AutoScrollPosition.Y);
.
.//old code
.
}
.
.//old code
.
}
.
.//old code
.
}
2-点击整条线,
&
3-移动和选择对象
使用Form1_MouseMove
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (super_form.super_action.Equals(action.move))
{
//new code
Point with_offset = new Point(e.X - 10 - AutoScrollPosition.X,
e.Y - 10 - AutoScrollPosition.Y);
//new code
.
.//old code
.
if (is_selected && e.Button == m)
{
selected_recatngle.Location = e.Location;
if (selected_type.Equals("shape"))
{
shape_list.ElementAt<shapes>(selected).translateX = e.X- AutoScrollPosition.X;
shape_list.ElementAt<shapes>(selected).translateY = e.Y- AutoScrollPosition.Y;
}
}
}
}
要选择对象,我们将使用Form1_MouseClick
private void Form1_MouseClick(object sender, MouseEventArgs e)
{
.
.//old code
.
else if (super_form.super_action.Equals(action.move))
{
.
.//old code
.
//new hand
Point with_offset = new Point(e.X - 10 - AutoScrollPosition.X,
e.Y - 10 - AutoScrollPosition.Y);
//new hand
.
.//old code
.
}
.
.//old code
.
}
4-绘制对象(星形、心形)
使用Form1_MouseClick
private void Form1_MouseClick(object sender, MouseEventArgs e)
{
if (super_form.super_action.Equals(action.star))
{
shapes sh = new shapes();
star st = new star();
sh = st;
//new code
sh.translateX = e.X- AutoScrollPosition.X;
sh.translateY = e.Y - AutoScrollPosition.Y;
//new code
shape_list.Add(st);
Invalidate();
}
else if (super_form.super_action.Equals(action.heart))
{
shapes sh = new shapes();
heart h = new heart();
sh = h;
//new code
sh.translateX = e.X- AutoScrollPosition.X;
sh.translateY = e.Y- AutoScrollPosition.Y;
//new code
shape_list.Add(h);
Invalidate();
}
.
.//old code
.
}
5-绘制线条
使用line_action
private void line_action(MouseEventArgs e)
{
.
.//old code
.
//new hand
Point_line.X = e.Location.X - AutoScrollPosition.X;
Point_line.Y = e.Location.Y - AutoScrollPosition.Y;
//new hand
.
.//old code
.
}
9-启用快捷键(空格键)以使用新的手形工具。
我们将使用 Adobe Reader、Adobe Illustrator、Adobe Photoshop 中的手形工具快捷键,即仅使用空格键来平移屏幕,按住空格键不会锁定在手形工具上,而只会平移屏幕,当用户完成平移(松开空格键)后,旧工具将返回给用户。
所以这个快捷键会很特殊,因为按住空格键后,旧工具会返回给用户。
因此在super_form
中,我们将使用一个公共静态变量,我们将其命名为old_action
。
public static action old_action;
然后,在tabControl1_KeyDown
函数内部,我们将为我们的新快捷键空格按钮添加一个条件。
private void tabControl1_KeyDown(object sender, KeyEventArgs e)
{
.
.//old code
.
//new code
else if (e.KeyData == Keys.Space)
{
// we only need this code to run once, as we need a hold action not just click,so we use an if
condition , if no if condition this code would continuously run as the user holds the space bar which
we don't need
if (super_action != action.hand)
{
old_action = super_action;//save the current action
super_action = action.hand;//change the current action to be hand
Cursor new_cursor=
new Cursor(drag_and_drop_and_delete.Properties.Resources.open_hand.Handle);
//load the open_hand_cursor to a new cursor variable
this.Cursor = new_cursor;
//and make the current cursor the open_hand_cursor
toolStrip2.Visible = false;
//make the line proprieties menu discussed in previous tut invisible
/////previous tut
//// https://codeproject.org.cn/Tips/1105143/tut-Draw-Lines-with-Circle-and-Rectangle-End
}
}
//new code
.
.//old code
.
}
现在我们将在用户停止按住空格键时选择旧工具,因此我们将在之前在教程中讨论过的tabcontrol
中创建一个KeyUp
函数。
所以,在super_form
中的tabControl1
的Properties
中
那么函数将是
private void tabControl1_KeyUp(object sender, KeyEventArgs e)
{
super_action = old_action;
//simply load the old tool that we have just saved it before to
be the current tool
}
但光标仍然是手形工具,所以我们必须进一步编辑tabControl1_KeyUp
以使其也能重置光标为旧操作的旧光标。
所以我们将使用old_action
来确定要加载的光标(旧工具的光标)
private void tabControl1_KeyUp(object sender, KeyEventArgs e)
{
super_action = old_action;
if(super_action.ToString()=="heart")
{
Cursor new_cursor =
new Cursor(drag_and_drop_and_delete.Properties.Resources.heart_cursor.Handle);
//load the cursor of the tool that corresponds to the action
this.Cursor = new_cursor;//load this cusor to be the cursor of the super_form
}
//same concept would work on other actions
else if (super_action.ToString() == "star")
{
Cursor new_cursor =
new Cursor(drag_and_drop_and_delete.Properties.Resources.star_cursor.Handle);
this.Cursor = new_cursor;
}
else if (super_action.ToString() == "line")
{
Cursor new_cursor =
new Cursor(drag_and_drop_and_delete.Properties.Resources.Line_cursor.Handle);
this.Cursor = new_cursor;
}
else if (super_action.ToString() == "move")
{
Cursor new_cursor =
new Cursor(drag_and_drop_and_delete.Properties.Resources.move__cursor.Handle);
this.Cursor = new_cursor;
}
else if (super_action.ToString() == "point")
{
Cursor new_cursor =
new Cursor(drag_and_drop_and_delete.Properties.Resources.point__cursor.Handle);
this.Cursor = new_cursor;
}
}
10-在Form1
中,使用OnPaint
来显示平移窗体时点周围的圆圈和线条轮廓。
我们将在Form1
中使用OnPaint
函数,以便在用户平移窗体时使用移动工具时,保留线条的轮廓。
因此在Form1
的OnPaint
函数中
之前我们只在super_form
中super_action = move
时才绘制轮廓。
但现在我们也会(所以我们会使用“或”逻辑)在old_action = move
时绘制,因为当手形工具被选中时,平移窗体时,旧动作会保存之前的工具,所以如果它是移动工具,当用户使用手形工具时,轮廓也会可见。
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
g.TranslateTransform(this.AutoScrollPosition.X, this.AutoScrollPosition.Y);
int counter_line = 0;
foreach (lines l in lines_list)
{
if (
super_form.super_action.Equals(action.move) ||
super_form.old_action.Equals(action.move)
)
{
Pen selection_pen_line = new Pen(Brushes.Blue, 2);
g.DrawPath(selection_pen_line, l.outline);
}
.
.//old code
.
}
同样的概念也适用于当用户同时使用point_mover_tool
和hand
工具时查看线条的点。
之前我们只在super_action = point
时绘制线条的点,但现在我们增加了一个新条件(使用“或”逻辑),即使用old_action = point
。
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
g.TranslateTransform(this.AutoScrollPosition.X, this.AutoScrollPosition.Y);
int counter_line = 0;
foreach (lines l in lines_list)
{
if (
super_form.super_action.Equals(action.move) ||
super_form.old_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);
if (
super_form.super_action.Equals(action.point) ||
super_form.old_action.Equals(action.point)
)
{
int count = 0;
foreach (GraphicsPath circle in l.circle_points_list)
{
.
.//old code
.
}