一、前言

就像开发的教程都从“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. Vulnhub-venom

    对于该靶机,注意利用了信息收集来的21端口和80端口,网站源码发现账户,ftp匿名登录密码猜测,维吉尼亚解密,后台管理员登录,CVE文件上传RCE漏洞利用反弹shell,提权有两中,利用版本内核提权和 ...

  2. 解决macOS无法验证“xxx”的开发者。你确定要打开它吗?

    前言 当 macOS 无法验证开发者时,有两种方式解决,可以通过以下步骤来打开 xxx 系统偏好设置: 打开"系统偏好设置". 选择"安全性与隐私". 在&qu ...

  3. TaskPyro:一个轻量级的 Python 任务调度和爬虫管理平台

    前言 推荐一款本人在使用的Python爬虫管理平台,亲测不错!!! TaskPyro 是什么? TaskPyro 是一个轻量级的 Python 任务调度平台,专注于提供简单易用的任务管理和爬虫调度解决 ...

  4. ORB算法介绍 Introduction to ORB (Oriented FAST and Rotated BRIEF)

    Introduction to ORB (Oriented FAST and Rotated BRIEF) 1. Introduction ORB(Oriented FAST and Rotated ...

  5. 【Linux】2.2 Linux安装

    安装 vm 和 Centos 学习 Linux 需要一个环境,我们需要创建一个虚拟机,然后在虚拟机上安装一个 Centos 系统来学习. 先安装 virtual machine ,vm12 再安装 L ...

  6. “你觉得客户需要”是杀死TA的最后一根稻草 | IPD集成产品开发

    这个米老鼠洗衣机,大家眼熟吗? 相信最近热衷于在网上冲浪的朋友们,对这款形似米老鼠的"懒人洗衣机"并不陌生,甚至算是小小地参与了一下这个产品研发项目.在海尔的周云杰总裁爆火出圈后, ...

  7. python操作PC版微信,给指定好友发信息(键鼠操作和复制粘贴相关库)

    主要用来"pyautogui"."pyperclip"两个模块 pyautogui 主要用于控制键盘和鼠标操作.详细参考https://blog.csdn.ne ...

  8. 康谋方案 | 从概念到生产的自动驾驶软件在环(SiL)测试解决方案

    一.自动驾驶软件在环(SiL)测试解决方案 自动驾驶软件在环(SiL)测试解决方案能够研究和验证高历程实验和恶劣驾驶环境下的AD系统的性能,支持云端和PC端操作,提供高保真度的仿真环境和传感器模型,实 ...

  9. 国产的 Java Solon v3.2.0 发布(央企信创的优选)

    Solon 框架! Solon 是新一代,Java 企业级应用开发框架.从零开始构建(No Java-EE),有灵活的接口规范与开放生态.采用商用友好的 Apache 2.0 开源协议,是" ...

  10. php/symfony执行生成密钥命令时报错Undefined constant 'OPENSSL_KEYTYPE_RSA'解决办法

    php/symfony执行 lexik:jwt:generate-keypair --overwrite命令生成密钥时报错Undefined constant 'OPENSSL_KEYTYPE_RSA ...