一、前言

就像开发的教程都从“Hello World!”开篇一样,系列开始,我们也从一个最最简单的功能开始:画一个能拖动的矩形。

顺便说一下,另一篇教程:(原创)[C#] GDI+ 之鼠标交互:原理、示例、一步步深入、性能优化 讲的更详细和深入,可以作为补充。

就让我们从一个能拖动的矩形开始我们的流程图开发之旅吧!

相信看完的你,一定会有所收获!

本文地址:https://www.cnblogs.com/lesliexin/p/18919737

二、先看效果

我们先看下本节所实现的效果:

可以看到,我们本节课程依次实现了三种效果:

添加一个可拖动的矩形

添加多个可拖动的矩形

添加多个不同颜色的可拖动的矩形

下面我们就来依次看一下这三种效果是怎么一步步实现的。

(注:系列完成时,将会将此演示DEMO程序及完整的源代码工程一起放到Github和Gitee上,为了更好的跟随教程进度,暂时请先参照每篇文章中的代码。)

三、实现效果1:一个可拖动的矩形

(一)原理

前言中说的那篇教程已经讲的很详细了,此处简略说下原理:

画一个矩形 -> 检测鼠标点击、移动等事件 -> 当鼠标点在矩形里时,移动鼠标的同时,计算矩形坐标并重新绘制矩形

详细的原理流程图如下:

(二)代码实操

下面我们就依据上面的原理流程图,来一步步编写代码实现。

1,设计器界面

设计器界面如下图所示,一个按钮、一个Panel,然后Panel实现了MouseDown、MouseMove、MouseUp事件。

(注:请忽略上面的绿色标签等控件,这是为了做统一化的演示Demo工具,与本篇文章不相关。)

2,添加矩形的代码

定义一个全局变量,因为好多地方都要用到或修改其值:

绘制代码很简单,就是GDI+的绘制矩形方法:

然后我们为“添加矩形”按钮点击事件添加添加矩形的代码:

3,鼠标点击事件实现

看上节的流程图,我们可以发现,首要的一步就是要判断鼠标有没有点到矩形上。

同样,我们定义两个全局变量,分别是鼠标点中矩形的标志、和鼠标的当前位置。

然后我们在MouseDown事件中,判断并对全局变量赋值。

4,鼠标松开事件实现

我们先看这个MouseUp事件,这个事件是重置标志和坐标的。

5,鼠标移动事件

这个MouseMove事件,就是本节的核心,我们参照流程图,一步步用代码实现即可。

到此,整个效果1已经完全实现了,大家可以尝试尝试。也附上完整的后台代码:

点击查看代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms; namespace FlowChartDemo
{
public partial class FormDemo01V1 : FormBase
{
public FormDemo01V1()
{
InitializeComponent();
DemoTitle = "第02节随课Demo Part1";
DemoNote = "效果:添加【一个】可拖动的矩形";
} /// <summary>
/// 画矩形
/// </summary>
/// <param name="g"></param>
void DrawRect(Graphics g)
{
g.Clear(panel1.BackColor);
g.FillRectangle(Brushes.Red,Rect);
} /// <summary>
/// 当前是否有鼠标按下,且有矩形被选中
/// </summary>
bool _isMouseDown = false;
/// <summary>
/// 最后一次鼠标的位置
/// </summary>
Point _lastMouseLocation = Point.Empty;
/// <summary>
/// 当前矩形
/// </summary>
Rectangle Rect = Rectangle.Empty; private void toolStripButton1_Click(object sender, EventArgs e)
{
if (!Rect.IsEmpty)
{
MessageBox.Show("已有矩形,无法再次添加");
return;
}
Rect = new Rectangle()
{
X = 50,
Y = 50,
Width = 100,
Height = 100,
};
//重绘所有矩形
DrawRect(panel1.CreateGraphics());
} private void panel1_MouseDown(object sender, MouseEventArgs e)
{
//当鼠标按下时 if (Rect.Contains(e.Location))
{
//证明鼠标点到了矩形 //设置状态及选中矩形
_isMouseDown = true;
_lastMouseLocation = e.Location;
}
} private void panel1_MouseMove(object sender, MouseEventArgs e)
{
//当鼠标移动时 if (_isMouseDown)
{
//当且仅当:有鼠标按下且有矩形被选中时,才进行后续操作 //改变选中矩形的位置信息,随着鼠标移动而移动 //计算鼠标位置变化信息
var moveX = e.Location.X - _lastMouseLocation.X;
var moveY = e.Location.Y - _lastMouseLocation.Y; //将选中形状的位置进行同样的变化
var oldXY = Rect.Location;
oldXY.Offset(moveX, moveY);
Rect = new Rectangle(oldXY, Rect.Size); //记录当前鼠标位置
_lastMouseLocation.Offset(moveX, moveY); //重绘所有矩形
DrawRect(panel1.CreateGraphics());
} } private void panel1_MouseUp(object sender, MouseEventArgs e)
{
//当鼠标松开时
if (_isMouseDown)
{
//当且仅当:有鼠标按下且有矩形被选中时,才进行后续操作 //重置相关记录信息
_isMouseDown = false;
_lastMouseLocation = Point.Empty;
}
}
} }

四、实现效果2:多个可拖动的矩形

(一)原理

基本的实现原理和效果1是一样的,不过是多了一步:判断点击的是多个矩形中的哪个矩形,然后在移动时仅移动选中的矩形。

话不多说,我们直接上代码实操。

(二)代码实操

下面我们就依据上面的原理流程图,来一步步编写代码实现。

1,设计器界面

设计器界面还是和效果1一样,不再赘述。

2,添加矩形的代码

因为涉及到多个矩形,所以我们先定义一个类,以标识矩形信息:

然后我们定义一个矩形列表的全局变量,用于存储添加的所有矩形信息:

绘制代码也作同步调整,遍历的绘制所有矩形:

注意看上面的代码,我们是在效果1的基础上来实现一个遍历调用的方法,而不是直接重新写一个遍历方法,或者直接使用GDI+的绘制矩形数组方法,这样写是为了后续进一步的抽象,因为我们的目的不是只绘制矩形,还有其它各种各样的形状。具体的我们后续教程会有讲解。

然后我们为“添加矩形”按钮点击事件添加添加矩形的代码,与效果1的区别是多了一步添加到矩形列表的操作:

3,鼠标点击事件实现

我们看这个MouseDown事件,这里与效果1的区别是要判断点到的是矩形列表中的哪个矩形。

代码如下,我们不多做赘述。

注意看,我们这里判断点到的是哪个矩形时,如果同一个坐标点下有多个矩形,我们是选择最后所添加的矩形。这个很好理解,就是PS中的图层一样,后添加的图层在上面。同样的,在上面绘制所有矩形时也是同样的逻辑,从旧到新,依次绘制,后添加的在上面。

4,鼠标松开事件实现

我们先看这个MouseUp事件,也效果1的差别是还要重置选中的矩形。

5,鼠标移动事件

这个MouseMove事件,同样,与效果1的差别就是要用选中的矩形

到此,整个效果2已经完全实现了,大家可以尝试尝试。也附上完整的后台代码:

点击查看代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms; namespace FlowChartDemo
{
public partial class FormDemo01V2 : FormBase
{
public FormDemo01V2()
{
InitializeComponent();
DemoTitle = "第02节随课Demo Part2";
DemoNote = "效果:添加【多个】可拖动的矩形";
} /// <summary>
/// 矩形定义
/// </summary>
public class RectShape
{
/// <summary>
/// 矩形ID
/// </summary>
public string Id { get; set; }
/// <summary>
/// 矩形位置和尺寸
/// </summary>
public Rectangle Rect { get; set; }
} /// <summary>
/// 当前界面矩形集合
/// </summary>
List<RectShape> Shapes = new List<RectShape>(); /// <summary>
/// 画一个矩形
/// </summary>
/// <param name="g"></param>
/// <param name="shape"></param>
void DrawShape(Graphics g,RectShape shape)
{
g.FillRectangle(Brushes.Red, shape.Rect);
g.DrawString(shape.Id, Font, Brushes.White, shape.Rect);
} /// <summary>
/// 重新绘制当前所有矩形
/// </summary>
/// <param name="g"></param>
void DrawAllShape(Graphics g)
{
g.Clear(panel1.BackColor) ;
foreach (var sp in Shapes)
{
DrawShape(g, sp);
}
} /// <summary>
/// 当前是否有鼠标按下,且有矩形被选中
/// </summary>
bool _isMouseDown = false;
/// <summary>
/// 最后一次鼠标的位置
/// </summary>
Point _lastMouseLocation = Point.Empty;
/// <summary>
/// 当前被鼠标选中的矩形
/// </summary>
RectShape _selectedShape = null; private void toolStripButton1_Click(object sender, EventArgs e)
{
var rs = new RectShape()
{
Id = "矩形" + (Shapes.Count + 1),
Rect = new Rectangle()
{
X = 50,
Y = 50,
Width = 100,
Height = 100,
},
};
Shapes.Add(rs);
//重绘所有矩形
DrawAllShape(panel1.CreateGraphics());
} private void panel1_MouseDown(object sender, MouseEventArgs e)
{
//当鼠标按下时 //取最上方的矩形,也就是最后添加的矩形
var sp = Shapes.FindLast(a => a.Rect.Contains(e.Location));
if (sp != null)
{
//证明取到了矩形 //设置状态及选中矩形
_isMouseDown = true;
_lastMouseLocation = e.Location;
_selectedShape = sp;
}
} private void panel1_MouseMove(object sender, MouseEventArgs e)
{
//当鼠标移动时 if (_isMouseDown)
{
//当且仅当:有鼠标按下且有矩形被选中时,才进行后续操作 //改变选中矩形的位置信息,随着鼠标移动而移动 //计算鼠标位置变化信息
var moveX = e.Location.X - _lastMouseLocation.X;
var moveY = e.Location.Y - _lastMouseLocation.Y; //将选中形状的位置进行同样的变化
var oldXY = _selectedShape.Rect.Location;
oldXY.Offset(moveX, moveY);
_selectedShape.Rect = new Rectangle(oldXY, _selectedShape.Rect.Size); //记录当前鼠标位置
_lastMouseLocation.Offset(moveX, moveY); //重绘所有矩形
DrawAllShape(panel1.CreateGraphics());
} } private void panel1_MouseUp(object sender, MouseEventArgs e)
{
//当鼠标松开时
if (_isMouseDown)
{
//当且仅当:有鼠标按下且有矩形被选中时,才进行后续操作 //重置相关记录信息
_isMouseDown = false;
_lastMouseLocation = Point.Empty;
_selectedShape = null;
}
}
} }

五、实现效果3:多个不同颜色的可拖动的矩形

(一)原理

同样,基本的实现原理和效果2是一样的,不过是多了一步:添加矩形时,给予不同的颜色。

话不多说,我们直接上代码实操。

(二)代码实操

下面我们就依据上面的原理流程图,来一步步编写代码实现。

1,设计器界面

设计器界面还是和效果1一样,不再赘述。

2,添加矩形的代码

定义一个全局变量,不再赘述:

然后我们添加一个根据序号也不同颜色的简单方法:

同样的,绘制矩形方法我们也稍作调整,增加设置颜色的步骤:

注意看,我们上面的代码是重写了个新方法并添加个2以作区分,然后绘制所有矩形的地方也同步调整为调用这个新方法。但实际环境中,不需要这样,直接在原方法上修改即可,这样绘制所有矩形的方法也不需要修改。这就是抽象的好处,当然这里体现不明显,但是我们在日常要保持抽象的思想。

然后“添加矩形”按钮点击事件代码也没有改动:

3,鼠标点击事件实现

这个MouseDown事件与效果2一样,不再赘述。

4,鼠标松开事件实现

这个MouseUp事件与效果2一样,不再赘述。

5,鼠标移动事件

这个MouseMove事件与效果2一样,不再赘述。

到此,整个效果3已经完全实现了,大家可以尝试尝试。也附上完整的后台代码:

点击查看代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms; namespace FlowChartDemo
{
public partial class FormDemo01V3 : FormBase
{
public FormDemo01V3()
{
InitializeComponent();
DemoTitle = "第02节随课Demo Part3";
DemoNote = "效果:添加【多个】可拖动的矩形,且矩形颜色不一样";
} /// <summary>
/// 矩形定义
/// </summary>
public class RectShape
{
/// <summary>
/// 矩形ID
/// </summary>
public string Id { get; set; }
/// <summary>
/// 矩形位置和尺寸
/// </summary>
public Rectangle Rect { get; set; }
} /// <summary>
/// 当前界面矩形集合
/// </summary>
List<RectShape> Shapes = new List<RectShape>(); /// <summary>
/// 画一个矩形(不同颜色)
/// </summary>
/// <param name="g"></param>
/// <param name="shape"></param>
void DrawShape2(Graphics g, RectShape shape)
{
var index = Shapes.FindIndex(a => a.Id == shape.Id);
g.FillRectangle(GetBrush(index), shape.Rect);
g.DrawString(shape.Id, Font, Brushes.White, shape.Rect);
} /// <summary>
/// 重新绘制当前所有矩形
/// </summary>
/// <param name="g"></param>
void DrawAllShape(Graphics g)
{
g.Clear(panel1.BackColor) ;
foreach (var sp in Shapes)
{
DrawShape2(g, sp);
}
} /// <summary>
/// 当前是否有鼠标按下,且有矩形被选中
/// </summary>
bool _isMouseDown = false;
/// <summary>
/// 最后一次鼠标的位置
/// </summary>
Point _lastMouseLocation = Point.Empty;
/// <summary>
/// 当前被鼠标选中的矩形
/// </summary>
RectShape _selectedShape = null; /// <summary>
/// 获取不同的背景颜色
/// </summary>
/// <param name="i"></param>
/// <returns></returns>
Brush GetBrush(int i)
{
switch (i)
{
case 0: return Brushes.Red;
case 1: return Brushes.Green;
case 2: return Brushes.Blue;
case 3: return Brushes.Orange;
case 4: return Brushes.Purple;
default: return Brushes.Red;
}
} private void toolStripButton1_Click(object sender, EventArgs e)
{
var rs = new RectShape()
{
Id = "矩形" + (Shapes.Count + 1),
Rect = new Rectangle()
{
X = 50,
Y = 50,
Width = 100,
Height = 100,
},
};
Shapes.Add(rs);
//重绘所有矩形
DrawAllShape(panel1.CreateGraphics());
} private void panel1_MouseDown(object sender, MouseEventArgs e)
{
//当鼠标按下时 //取最上方的矩形,也就是最后添加的矩形
var sp = Shapes.FindLast(a => a.Rect.Contains(e.Location));
if (sp != null)
{
//证明取到了矩形 //设置状态及选中矩形
_isMouseDown = true;
_lastMouseLocation = e.Location;
_selectedShape = sp;
}
} private void panel1_MouseMove(object sender, MouseEventArgs e)
{
//当鼠标移动时 if (_isMouseDown)
{
//当且仅当:有鼠标按下且有矩形被选中时,才进行后续操作 //改变选中矩形的位置信息,随着鼠标移动而移动 //计算鼠标位置变化信息
var moveX = e.Location.X - _lastMouseLocation.X;
var moveY = e.Location.Y - _lastMouseLocation.Y; //将选中形状的位置进行同样的变化
var oldXY = _selectedShape.Rect.Location;
oldXY.Offset(moveX, moveY);
_selectedShape.Rect = new Rectangle(oldXY, _selectedShape.Rect.Size); //记录当前鼠标位置
_lastMouseLocation.Offset(moveX, moveY); //重绘所有矩形
DrawAllShape(panel1.CreateGraphics());
} } private void panel1_MouseUp(object sender, MouseEventArgs e)
{
//当鼠标松开时
if (_isMouseDown)
{
//当且仅当:有鼠标按下且有矩形被选中时,才进行后续操作 //重置相关记录信息
_isMouseDown = false;
_lastMouseLocation = Point.Empty;
_selectedShape = null;
}
}
} }

六、结语

绘制可拖动的矩形,是一切的开始和基础,我们通过本篇教程,了解到了如何一步步由浅入深,如何保持抽象的思想,为后续的开发打好基础。

本篇文章没有什么复杂的代码,都很常见,一个复杂的系统、繁复的功能都是由这样一个个简单的组件有机的组合成的。我们下篇教程,将会讲解如何在形状之间添加一条连线,这个也是流程图的基础。

感谢大家的观看,本人水平有限,文章不足之处欢迎大家评论指正。

-[END]-

[原创]《C#高级GDI+实战:从零开发一个流程图》第02章:画一个矩形,能拖动!的更多相关文章

  1. Unity 2D游戏开发高速入门第1章创建一个简单的2D游戏

    Unity 2D游戏开发高速入门第1章创建一个简单的2D游戏 即使是如今,非常多初学游戏开发的同学.在谈到Unity的时候.依旧会觉得Unity仅仅能用于制作3D游戏的. 实际上.Unity在2013 ...

  2. 【原创】新手入门一篇就够:从零开发移动端IM

    一.前言 IM发展至今,已是非常重要的互联网应用形态之一,尤其移动互联网时代,它正以无与论比的优势降低了沟通成本和沟通代价,对各种应用形态产生了深远影响. 做为IM开发者或即将成为IM开发者的技术人员 ...

  3. 《Android Studio开发实战 从零基础到App上线》资源下载和内容勘误

    转载于:https://blog.csdn.net/aqi00/article/details/73065392 资源下载 下面是<Android Studio开发实战 从零基础到App上线&g ...

  4. 手牵手,使用uni-app从零开发一款视频小程序 (系列下 开发实战篇)

    系列文章 手牵手,使用uni-app从零开发一款视频小程序 (系列上 准备工作篇) 手牵手,使用uni-app从零开发一款视频小程序 (系列下 开发实战篇) 扫码体验,先睹为快 可以扫描下微信小程序的 ...

  5. 跟着老男孩一步步学习Shell高级编程实战

    原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://oldboy.blog.51cto.com/2561410/1264627 本sh ...

  6. (转)跟着老男孩一步步学习Shell高级编程实战

    原文:http://oldboy.blog.51cto.com/2561410/1264627/  跟着老男孩一步步学习Shell高级编程实战 原创作品,允许转载,转载时请务必以超链接形式标明文章 原 ...

  7. 《IM开发新手入门一篇就够:从零开发移动端IM》

        登录 立即注册 TCP/IP详解 资讯 动态 社区 技术精选 首页   即时通讯网›专项技术区›IM开发新手入门一篇就够:从零开发移动端IM   帖子 打赏 分享 发表评论162     想开 ...

  8. 适合新手:从零开发一个IM服务端(基于Netty,有完整源码)

    本文由“yuanrw”分享,博客:juejin.im/user/5cefab8451882510eb758606,收录时内容有改动和修订. 0.引言 站长提示:本文适合IM新手阅读,但最好有一定的网络 ...

  9. Shell高级编程视频教程-跟着老男孩一步步学习Shell高级编程实战视频教程

    Shell高级编程视频教程-跟着老男孩一步步学习Shell高级编程实战视频教程 教程简介: 本教程共71节,主要介绍了shell的相关知识教程,如shell编程需要的基础知识储备.shell脚本概念介 ...

  10. 《疯狂iOS讲义(下)——iPhone/iPad高级应用与手游开发(含CD光盘1张)》

    <疯狂iOS讲义(下)——iPhone/iPad高级应用与手游开发(含CD光盘1张)> 基本信息 作者: 李刚    肖文吉 出版社:电子工业出版社 ISBN:9787121224379 ...

随机推荐

  1. 程序员必看 Linux 常用命令(重要)

    文件操作命令 find find 用于在指定目录下查找文件或子目录,如果不指定查找目录,则在当前目录下查找 命令格式:find path -option [-print] [ -exec/-ok co ...

  2. k8s报错Error: template: nvidia-device-plugin/templates/gfd.yml:22:19: executing "nvidia-device-plugin/templates/gfd.yml" at <.Subcharts.gfd>: nil pointer evaluating interface {}.gfd

    前言 在安装 kubernetes 插件 k8s-device-plugin时,报错: Error: template: nvidia-device-plugin/templates/gfd.yml: ...

  3. 包装类--java进阶day05

    1.包装类 比如要让s+100,输出223.如果直接相加,结果是123100,这时就可以将s转换为包装类,然后再用包装类进行相加 2.包装类类型 3.手动拆/装箱 我们这里只介绍Integer,其他包 ...

  4. 小白必看的java完整下载攻略!(在Typora中有图片参考)

    Java下载 在浏览器上搜索JDK(2024年最新版是22,本人下载的是21) 点击官网下载,会跳到Oracle官网,需要注册账号才可下载 根据自己的电脑型号选择下载(本人下载的是64的) 正常情况下 ...

  5. SMU Autumn 2024 Trial 2

    A. Stand-up Comedian 只要a不为0,那么由于b和c是相互制约的,比如2 5 10 6这个样例,讲完两个笑话以后两个人的容量为2 2,可以选择一个人+1,一个人-1,那么就可进行mi ...

  6. virtualbox怎么设置鼠标在物理机和虚拟机间切换

    一.先打开管理,在"全局设定"中选择"热键",会看到如下图所示的内容,默认使用右ctrl键让鼠标离开虚拟机

  7. MySQL 中 varchar 和 char 有什么区别?

    MySQL 中 varchar 和 char 的区别 在 MySQL 中,VARCHAR 和 CHAR 是两种常用的字符串类型,它们在存储方式.长度限制和使用场景等方面存在显著区别. 1. 定义与存储 ...

  8. 备份一个迭代查找TreeViewItem的辅助函数

    private TreeViewItem FindTreeItem(TreeViewItem item, Func<TreeViewItem, bool> compare) { if (i ...

  9. EFCore Study(番外1)——单向导航属性

    一.单向导航属性场景 1.比如公司OA系统有很多相关的审批流程,比如离职(Leave).请假(relex).加班,每个流程都有多级审批人,每个审批人都是User 一个人(User)--多个审批流程的多 ...

  10. 等保2.0>Windows下实现MySQL数据库自动备份

    说明: MySQL数据库安装目录:C:\Program Files\mysql-5.7.37-winx64\mysql-5.7.37-winx64 MySQL数据库存放目录:C:\Program Fi ...