[教程 3] 使用 C# 进行图形拖放和删除对象的程序






4.50/5 (3投票s)
教程第三部分,介绍使用 C# 和 GDI 及 SVG 进行图形拖放和删除对象的程序
引言
这是本系列教程的第三部分,介绍如何使用 C# 在 Windows 窗体中构建图形程序,并将其导出为矢量格式。
......此外,您还将了解如何移动、删除、**Ctrl+Z** 您的矢量图形,并将其保存为一种特殊的格式,以便您的程序可以再次读取。
此外,我们还将学习如何保存 XML 文件……如何以 Verilog 格式导出……如何使用星形算法……如何使用手形工具……如何手动创建 **Ctrl+Z** 技术。
您将能够构建什么
- https://drive.google.com/folderview?id=0B739QmcCMLMHVU1DbHhIQlZ3MTA&usp=sharing
- https://www.youtube.com/watch?v=6hXJ1EoAYc0
在本教程中,我们将从介绍如何让您的 C# 程序绘制特定的 SVG 路径开始。
接续上次的进度……今天,我们将讨论如何拖放对象。
- 首先进行一些修改
- 添加鼠标移动功能
背景
Using the Code
1. 那么,让我们开始对我们之前的代码进行一些修改
由于我们要移动对象,因此需要删除任何类型的缓冲,因此让我们在我们的 `main function` 中编写以下代码行:
this.DoubleBuffered = true; //will remove any buffering
此外,让我们添加一些 `private data members` 来指示哪个对象被选中,以及是否已经选中任何元素。
int selected=0;
bool is_selected = false;
添加一个新的键盘监听器来监听移动命令,所以在 `Form1_KeyDown` 中:
else if (e.KeyData == Keys.M)
{
action = "move";
}
我们还将需要一个成员来保存左键单击的值。
另一个成员用于保存一个 `bool`,该 `bool` 标识您是否是第一次拖动一个对象以创建循环,并知道您选择了哪个对象……否则,每次移动鼠标都会循环一次,并创建一个用作选择边界框的矩形。
私有成员
MouseButtons m = MouseButtons.Left;
bool first = true;
RectangleF selected_recatngle = new RectangleF();
此外,我还修改了心形的 `sData`,使其变为:
private string sData = "M97.5,181.5c-4.5-5-15.6-14.8-24.8-21.7c-27.2-20.5-30.9-23.
5-41.9-33.7c-20.4-18.7-29-37.6-29-63.1c0-12.5,0.9-17.3,4.4-24.6C12,25.9,20.8,16.7,
31.9,11c7.9-4,11.8-5.8,25-5.9c13.8-0.1,16.7,1.5,24.8,6c9.9,5.4,20.1,17,22.2,25.3l1.3,
5.1l3.2-7c18.1-39.6,75.9-39,96,1c6.4,12.7,7.1,39.8,1.4,55.1c-7.4,19.9-21.2,35.1-53.3,
58.4c-21,15.3-44.8,38.3-46.4,41.6C104.2,194.2,106,191,97.5,181.5z";
现在,让我们通过转到 `designer` 并将属性>>背景颜色>>更改为白色,使我们的应用程序更像一个图形应用程序。
2. 现在,让我们来处理我们的拖放功能。
首先,转到窗体设计器并添加一个 `mouse move action`。
然后,如果我们转到后台代码,我们将看到已创建 `private void Form1_MouseMove(object sender, MouseEventArgs e)`。
我们需要在用户使用此方法后开始拖动……因此,选择技术仅在用户第一次使用此函数时发生……一旦此技术完成,无论何时用户继续选择对象,程序都无需重新计算已选择的对象……所以总结一下……程序仅在用户第一次使用该函数时循环遍历列表……当他继续移动该对象时,程序无需重新计算哪个对象……这将通过用户第一次使用该函数时的条件来实现。
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (first == true && e.Button == m && action.Equals("move"))
{
}
}
这将检查这是您第一次进入此函数,以及您是否使用左鼠标按钮进入此函数,以及该操作是否为移动操作。
现在我们需要遍历我们的列表以了解选择了哪个对象……所以先编写 `foreach` 循环进行迭代……然后,我们将设置条件以查看选择点是否在任何形状内。
if (sh.draw_svg().Path(render).GetBounds().Contains(e.Location))
然后在此函数中,让我们定义将用于定义矩形边界的矩形,并将选定的 `int` 设置为计数器以了解选择了哪个对象,并将选择的 `bool` 设置为 `true`,如果发生这种情况,则打破 `foreach` 循环,否则将 `is_selected` 设置为 `false`,当然永远不要忘记增加你的计数器,如果你进入了这个条件,那么这是你的第一次……那么下次你移动对象时,你就不需要进入这个条件了,所以只需将 `first` 设置为 `false`。
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (first == true && e.Button == m && action.Equals("move"))
{
int counter = 0;
foreach (shapes sh in shape_list)
{
if (sh.draw_svg().Path(render).GetBounds().Contains(e.Location))
{
selected_recatngle = sh.draw_svg().Path(render).GetBounds();
selected = counter;
is_selected = true;
break;
}
else
{
is_selected = false;
}
counter++;
}
first = false;
}
以上所有只是为了定义所选的形状……我们确实需要移动它……只需先更新边界矩形……然后我们只需修改 `shape` 类中的 `translateX` 和 `translateY` 属性……并调用 `paint` 函数。
如果对象未被选中,它将进入下一个 `else` 条件,将 `first` 布尔值重置为 `false`……这样,当您下次选择另一个形状时,您将知道您选择了哪个对象。
if (is_selected == true && e.Button == m && action.Equals("move"))
{
selected_recatngle.Location = e.Location;
shape_list.ElementAt<shapes>(selected).translateX = e.X;
shape_list.ElementAt<shapes>(selected).translateY = e.Y;
Invalidate();
}
else
{
first = true;
}
所以最终的 `mouse move` 将是这样的:
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (first == true && e.Button == m && action.Equals("move"))
{
int counter = 0;
foreach (shapes sh in shape_list)
{
// if (sh.draw_svg().Path(render).GetBounds().Contains(e.Location))
//inefficient
if (sh.draw_svg().Path(render).IsVisible(e.Location))//more efficient
{
selected_recatngle = sh.draw_svg().Path(render).GetBounds();
selected = counter;
is_selected = true;
break;
}
else
{
is_selected = false;
}
counter++;
}
first = false;
}
if (is_selected == true && e.Button == m && action.Equals("move"))
{
selected_recatngle.Location = e.Location;
shape_list.ElementAt<shapes>(selected).translateX = e.X;
shape_list.ElementAt<shapes>(selected).translateY = e.Y;
Invalidate();
}
else
{
first = true;
}
}
现在让我们处理在更新的形状成员中的 `translateX` 和 `translateY`……只需在 `heart` 和 `star` 的 `draw svg` 中添加此矩阵……以便在返回之前移动返回值,并应用此 `transform`。
Matrix m = new Matrix();
m.Translate(translateX, translateY, MatrixOrder.Append);
alu.Transform(m);
因此,`heart` 和 `star` 中的最终 `draw_svg` 将是:
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);
//modification
Matrix m = new Matrix();
m.Translate(translateX, translateY, MatrixOrder.Append);
alu.Transform(m);
//modification
region = new Region(pa.Path(render));
return pa;
}
现在让我们绘制选定的矩形……让它成为一个虚线红色矩形……在 `onpaint` 函数中,添加此代码:
Point p_temp = new Point((int)selected_recatngle.Location.X, (int)selected_recatngle.Location.Y);
Size sssize = new System.Drawing.Size((int)selected_recatngle.Size.Width,
(int)selected_recatngle.Size.Height);
Rectangle rrrr = new Rectangle(p_temp, sssize);
Pen selection_pen = new Pen(Brushes.Red, 2);
selection_pen.DashStyle = DashStyle.Dot;
e.Graphics.DrawRectangle(selection_pen, rrrr);
以上行的目的是简单地将 `RectangleF` 转换为一个普通的矩形进行绘制,然后使用红色虚线笔进行绘制。
现在要删除特定的对象,只需修改 `Form1_KeyDown` 处理程序:
else if (e.KeyData == Keys.Delete && is_selected==true)
{
shape_list.RemoveAt(selected);
RectangleF null_rectangle = new RectangleF();
selected_recatngle = null_rectangle;
is_selected = false;
Invalidate();
}
正如我们在鼠标移动中之前确定的选定项一样,只需将其从列表中删除,并将选择矩形点设置为 `null`。
我非常想知道您对这些教程的看法……它们是否达到了您的期望??……我非常想听到您的反馈。
历史
- 2016年3月8日:初始版本