找回密码
 立即注册
首页 业界区 业界 [原创]《C#高级GDI+实战:从零开发一个流程图》第02章: ...

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

欤夤 2025-6-10 12:52:31
一、前言

就像开发的教程都从“Hello World!”开篇一样,系列开始,我们也从一个最最简单的功能开始:画一个能拖动的矩形。
顺便说一下,另一篇教程:(原创)[C#] GDI+ 之鼠标交互:原理、示例、一步步深入、性能优化 讲的更详细和深入,可以作为补充。
就让我们从一个能拖动的矩形开始我们的流程图开发之旅吧!
相信看完的你,一定会有所收获!
本文地址:https://www.cnblogs.com/lesliexin/p/18919737
二、先看效果

我们先看下本节所实现的效果:
可以看到,我们本节课程依次实现了三种效果:
添加一个可拖动的矩形
添加多个可拖动的矩形
添加多个不同颜色的可拖动的矩形
下面我们就来依次看一下这三种效果是怎么一步步实现的。
(注:系列完成时,将会将此演示DEMO程序及完整的源代码工程一起放到Github和Gitee上,为了更好的跟随教程进度,暂时请先参照每篇文章中的代码。)
三、实现效果1:一个可拖动的矩形

(一)原理

前言中说的那篇教程已经讲的很详细了,此处简略说下原理:
画一个矩形 -> 检测鼠标点击、移动等事件 -> 当鼠标点在矩形里时,移动鼠标的同时,计算矩形坐标并重新绘制矩形
详细的原理流程图如下:
1.png

(二)代码实操

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

设计器界面如下图所示,一个按钮、一个Panel,然后Panel实现了MouseDown、MouseMove、MouseUp事件。
(注:请忽略上面的绿色标签等控件,这是为了做统一化的演示Demo工具,与本篇文章不相关。)
2.png

2,添加矩形的代码

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

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

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

3,鼠标点击事件实现

看上节的流程图,我们可以发现,首要的一步就是要判断鼠标有没有点到矩形上。
同样,我们定义两个全局变量,分别是鼠标点中矩形的标志、和鼠标的当前位置。
6.png

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

4,鼠标松开事件实现

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

5,鼠标移动事件

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

到此,整个效果1已经完全实现了,大家可以尝试尝试。也附上完整的后台代码:
点击查看代码
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Data;
  5. using System.Drawing;
  6. using System.Linq;
  7. using System.Text;
  8. using System.Threading.Tasks;
  9. using System.Windows.Forms;
  10. namespace FlowChartDemo
  11. {
  12.     public partial class FormDemo01V1 : FormBase
  13.     {
  14.         public FormDemo01V1()
  15.         {
  16.             InitializeComponent();
  17.             DemoTitle = "第02节随课Demo  Part1";
  18.             DemoNote = "效果:添加【一个】可拖动的矩形";
  19.         }
  20.         /// <summary>
  21.         /// 画矩形
  22.         /// </summary>
  23.         /// <param name="g"></param>
  24.         void DrawRect(Graphics g)
  25.         {
  26.             g.Clear(panel1.BackColor);
  27.             g.FillRectangle(Brushes.Red,Rect);
  28.         }
  29.         /// <summary>
  30.         /// 当前是否有鼠标按下,且有矩形被选中
  31.         /// </summary>
  32.         bool _isMouseDown = false;
  33.         /// <summary>
  34.         /// 最后一次鼠标的位置
  35.         /// </summary>
  36.         Point _lastMouseLocation = Point.Empty;
  37.         /// <summary>
  38.         /// 当前矩形
  39.         /// </summary>
  40.         Rectangle Rect = Rectangle.Empty;
  41.         private void toolStripButton1_Click(object sender, EventArgs e)
  42.         {
  43.             if (!Rect.IsEmpty)
  44.             {
  45.                 MessageBox.Show("已有矩形,无法再次添加");
  46.                 return;
  47.             }
  48.             Rect = new Rectangle()
  49.             {
  50.                 X = 50,
  51.                 Y = 50,
  52.                 Width = 100,
  53.                 Height = 100,
  54.             };
  55.             //重绘所有矩形
  56.             DrawRect(panel1.CreateGraphics());
  57.         }
  58.         private void panel1_MouseDown(object sender, MouseEventArgs e)
  59.         {
  60.             //当鼠标按下时
  61.             if (Rect.Contains(e.Location))
  62.             {
  63.                 //证明鼠标点到了矩形
  64.                 //设置状态及选中矩形
  65.                 _isMouseDown = true;
  66.                 _lastMouseLocation = e.Location;
  67.             }
  68.         }
  69.         private void panel1_MouseMove(object sender, MouseEventArgs e)
  70.         {
  71.             //当鼠标移动时
  72.             if (_isMouseDown)
  73.             {
  74.                 //当且仅当:有鼠标按下且有矩形被选中时,才进行后续操作
  75.                 //改变选中矩形的位置信息,随着鼠标移动而移动
  76.                 //计算鼠标位置变化信息
  77.                 var moveX = e.Location.X - _lastMouseLocation.X;
  78.                 var moveY = e.Location.Y - _lastMouseLocation.Y;
  79.                 //将选中形状的位置进行同样的变化
  80.                 var oldXY = Rect.Location;
  81.                 oldXY.Offset(moveX, moveY);
  82.                 Rect = new Rectangle(oldXY, Rect.Size);
  83.                 //记录当前鼠标位置
  84.                 _lastMouseLocation.Offset(moveX, moveY);
  85.                 //重绘所有矩形
  86.                 DrawRect(panel1.CreateGraphics());
  87.             }
  88.         }
  89.         private void panel1_MouseUp(object sender, MouseEventArgs e)
  90.         {
  91.             //当鼠标松开时
  92.             if (_isMouseDown)
  93.             {
  94.                 //当且仅当:有鼠标按下且有矩形被选中时,才进行后续操作
  95.                 //重置相关记录信息
  96.                 _isMouseDown = false;
  97.                 _lastMouseLocation = Point.Empty;
  98.             }
  99.         }
  100.     }
  101. }
复制代码
四、实现效果2:多个可拖动的矩形

(一)原理

基本的实现原理和效果1是一样的,不过是多了一步:判断点击的是多个矩形中的哪个矩形,然后在移动时仅移动选中的矩形。
话不多说,我们直接上代码实操。
(二)代码实操

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

设计器界面还是和效果1一样,不再赘述。
2,添加矩形的代码

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

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

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

注意看上面的代码,我们是在效果1的基础上来实现一个遍历调用的方法,而不是直接重新写一个遍历方法,或者直接使用GDI+的绘制矩形数组方法,这样写是为了后续进一步的抽象,因为我们的目的不是只绘制矩形,还有其它各种各样的形状。具体的我们后续教程会有讲解。
然后我们为“添加矩形”按钮点击事件添加添加矩形的代码,与效果1的区别是多了一步添加到矩形列表的操作:
13.png

3,鼠标点击事件实现

我们看这个MouseDown事件,这里与效果1的区别是要判断点到的是矩形列表中的哪个矩形。
代码如下,我们不多做赘述。
14.png

15.png

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

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

5,鼠标移动事件

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

到此,整个效果2已经完全实现了,大家可以尝试尝试。也附上完整的后台代码:
点击查看代码
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Data;
  5. using System.Drawing;
  6. using System.Linq;
  7. using System.Text;
  8. using System.Threading.Tasks;
  9. using System.Windows.Forms;
  10. namespace FlowChartDemo
  11. {
  12.     public partial class FormDemo01V2 : FormBase
  13.     {
  14.         public FormDemo01V2()
  15.         {
  16.             InitializeComponent();
  17.             DemoTitle = "第02节随课Demo  Part2";
  18.             DemoNote = "效果:添加【多个】可拖动的矩形";
  19.         }
  20.         /// <summary>
  21.         /// 矩形定义
  22.         /// </summary>
  23.         public class RectShape
  24.         {
  25.             /// <summary>
  26.             /// 矩形ID
  27.             /// </summary>
  28.             public string Id { get; set; }
  29.             /// <summary>
  30.             /// 矩形位置和尺寸
  31.             /// </summary>
  32.             public Rectangle Rect { get; set; }
  33.         }
  34.         /// <summary>
  35.         /// 当前界面矩形集合
  36.         /// </summary>
  37.         List<RectShape> Shapes = new List<RectShape>();
  38.         /// <summary>
  39.         /// 画一个矩形
  40.         /// </summary>
  41.         /// <param name="g"></param>
  42.         /// <param name="shape"></param>
  43.         void DrawShape(Graphics g,RectShape shape)
  44.         {
  45.             g.FillRectangle(Brushes.Red, shape.Rect);
  46.             g.DrawString(shape.Id, Font, Brushes.White, shape.Rect);
  47.         }
  48.         /// <summary>
  49.         /// 重新绘制当前所有矩形
  50.         /// </summary>
  51.         /// <param name="g"></param>
  52.         void DrawAllShape(Graphics g)
  53.         {
  54.             g.Clear(panel1.BackColor) ;
  55.             foreach (var sp in Shapes)
  56.             {
  57.                 DrawShape(g, sp);
  58.             }
  59.         }
  60.         /// <summary>
  61.         /// 当前是否有鼠标按下,且有矩形被选中
  62.         /// </summary>
  63.         bool _isMouseDown = false;
  64.         /// <summary>
  65.         /// 最后一次鼠标的位置
  66.         /// </summary>
  67.         Point _lastMouseLocation = Point.Empty;
  68.         /// <summary>
  69.         /// 当前被鼠标选中的矩形
  70.         /// </summary>
  71.         RectShape _selectedShape = null;
  72.         private void toolStripButton1_Click(object sender, EventArgs e)
  73.         {
  74.             var rs = new RectShape()
  75.             {
  76.                 Id = "矩形" + (Shapes.Count + 1),
  77.                 Rect = new Rectangle()
  78.                 {
  79.                     X = 50,
  80.                     Y = 50,
  81.                     Width = 100,
  82.                     Height = 100,
  83.                 },
  84.             };
  85.             Shapes.Add(rs);
  86.             //重绘所有矩形
  87.             DrawAllShape(panel1.CreateGraphics());
  88.         }
  89.         private void panel1_MouseDown(object sender, MouseEventArgs e)
  90.         {
  91.             //当鼠标按下时
  92.             //取最上方的矩形,也就是最后添加的矩形
  93.             var sp = Shapes.FindLast(a => a.Rect.Contains(e.Location));
  94.             if (sp != null)
  95.             {
  96.                 //证明取到了矩形
  97.                 //设置状态及选中矩形
  98.                 _isMouseDown = true;
  99.                 _lastMouseLocation = e.Location;
  100.                 _selectedShape = sp;
  101.             }
  102.         }
  103.         private void panel1_MouseMove(object sender, MouseEventArgs e)
  104.         {
  105.             //当鼠标移动时
  106.             if (_isMouseDown)
  107.             {
  108.                 //当且仅当:有鼠标按下且有矩形被选中时,才进行后续操作
  109.                 //改变选中矩形的位置信息,随着鼠标移动而移动
  110.                 //计算鼠标位置变化信息
  111.                 var moveX = e.Location.X - _lastMouseLocation.X;
  112.                 var moveY = e.Location.Y - _lastMouseLocation.Y;
  113.                 //将选中形状的位置进行同样的变化
  114.                 var oldXY = _selectedShape.Rect.Location;
  115.                 oldXY.Offset(moveX, moveY);
  116.                 _selectedShape.Rect = new Rectangle(oldXY, _selectedShape.Rect.Size);
  117.                 //记录当前鼠标位置
  118.                 _lastMouseLocation.Offset(moveX, moveY);
  119.                 //重绘所有矩形
  120.                 DrawAllShape(panel1.CreateGraphics());
  121.             }
  122.         }
  123.         private void panel1_MouseUp(object sender, MouseEventArgs e)
  124.         {
  125.             //当鼠标松开时
  126.             if (_isMouseDown)
  127.             {
  128.                 //当且仅当:有鼠标按下且有矩形被选中时,才进行后续操作
  129.                 //重置相关记录信息
  130.                 _isMouseDown = false;
  131.                 _lastMouseLocation = Point.Empty;
  132.                 _selectedShape = null;
  133.             }
  134.         }
  135.     }
  136. }
复制代码
五、实现效果3:多个不同颜色的可拖动的矩形

(一)原理

同样,基本的实现原理和效果2是一样的,不过是多了一步:添加矩形时,给予不同的颜色。
话不多说,我们直接上代码实操。
(二)代码实操

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

设计器界面还是和效果1一样,不再赘述。
2,添加矩形的代码

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

19.png

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

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

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

3,鼠标点击事件实现

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

24.png

4,鼠标松开事件实现

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

5,鼠标移动事件

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

到此,整个效果3已经完全实现了,大家可以尝试尝试。也附上完整的后台代码:
点击查看代码
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Data;
  5. using System.Drawing;
  6. using System.Linq;
  7. using System.Text;
  8. using System.Threading.Tasks;
  9. using System.Windows.Forms;
  10. namespace FlowChartDemo
  11. {
  12.     public partial class FormDemo01V3 : FormBase
  13.     {
  14.         public FormDemo01V3()
  15.         {
  16.             InitializeComponent();
  17.             DemoTitle = "第02节随课Demo  Part3";
  18.             DemoNote = "效果:添加【多个】可拖动的矩形,且矩形颜色不一样";
  19.         }
  20.         /// <summary>
  21.         /// 矩形定义
  22.         /// </summary>
  23.         public class RectShape
  24.         {
  25.             /// <summary>
  26.             /// 矩形ID
  27.             /// </summary>
  28.             public string Id { get; set; }
  29.             /// <summary>
  30.             /// 矩形位置和尺寸
  31.             /// </summary>
  32.             public Rectangle Rect { get; set; }
  33.         }
  34.         /// <summary>
  35.         /// 当前界面矩形集合
  36.         /// </summary>
  37.         List<RectShape> Shapes = new List<RectShape>();
  38.         /// <summary>
  39.         /// 画一个矩形(不同颜色)
  40.         /// </summary>
  41.         /// <param name="g"></param>
  42.         /// <param name="shape"></param>
  43.         void DrawShape2(Graphics g, RectShape shape)
  44.         {
  45.             var index = Shapes.FindIndex(a => a.Id == shape.Id);
  46.             g.FillRectangle(GetBrush(index), shape.Rect);
  47.             g.DrawString(shape.Id, Font, Brushes.White, shape.Rect);
  48.         }
  49.         /// <summary>
  50.         /// 重新绘制当前所有矩形
  51.         /// </summary>
  52.         /// <param name="g"></param>
  53.         void DrawAllShape(Graphics g)
  54.         {
  55.             g.Clear(panel1.BackColor) ;
  56.             foreach (var sp in Shapes)
  57.             {
  58.                 DrawShape2(g, sp);
  59.             }
  60.         }
  61.         /// <summary>
  62.         /// 当前是否有鼠标按下,且有矩形被选中
  63.         /// </summary>
  64.         bool _isMouseDown = false;
  65.         /// <summary>
  66.         /// 最后一次鼠标的位置
  67.         /// </summary>
  68.         Point _lastMouseLocation = Point.Empty;
  69.         /// <summary>
  70.         /// 当前被鼠标选中的矩形
  71.         /// </summary>
  72.         RectShape _selectedShape = null;
  73.         /// <summary>
  74.         /// 获取不同的背景颜色
  75.         /// </summary>
  76.         /// <param name="i"></param>
  77.         /// <returns></returns>
  78.         Brush GetBrush(int i)
  79.         {
  80.             switch (i)
  81.             {
  82.                 case 0: return Brushes.Red;
  83.                 case 1: return Brushes.Green;
  84.                 case 2: return Brushes.Blue;
  85.                 case 3: return Brushes.Orange;
  86.                 case 4: return Brushes.Purple;
  87.                 default: return Brushes.Red;
  88.             }
  89.         }
  90.         private void toolStripButton1_Click(object sender, EventArgs e)
  91.         {
  92.             var rs = new RectShape()
  93.             {
  94.                 Id = "矩形" + (Shapes.Count + 1),
  95.                 Rect = new Rectangle()
  96.                 {
  97.                     X = 50,
  98.                     Y = 50,
  99.                     Width = 100,
  100.                     Height = 100,
  101.                 },
  102.             };
  103.             Shapes.Add(rs);
  104.             //重绘所有矩形
  105.             DrawAllShape(panel1.CreateGraphics());
  106.         }
  107.         private void panel1_MouseDown(object sender, MouseEventArgs e)
  108.         {
  109.             //当鼠标按下时
  110.             //取最上方的矩形,也就是最后添加的矩形
  111.             var sp = Shapes.FindLast(a => a.Rect.Contains(e.Location));
  112.             if (sp != null)
  113.             {
  114.                 //证明取到了矩形
  115.                 //设置状态及选中矩形
  116.                 _isMouseDown = true;
  117.                 _lastMouseLocation = e.Location;
  118.                 _selectedShape = sp;
  119.             }
  120.         }
  121.         private void panel1_MouseMove(object sender, MouseEventArgs e)
  122.         {
  123.             //当鼠标移动时
  124.             if (_isMouseDown)
  125.             {
  126.                 //当且仅当:有鼠标按下且有矩形被选中时,才进行后续操作
  127.                 //改变选中矩形的位置信息,随着鼠标移动而移动
  128.                 //计算鼠标位置变化信息
  129.                 var moveX = e.Location.X - _lastMouseLocation.X;
  130.                 var moveY = e.Location.Y - _lastMouseLocation.Y;
  131.                 //将选中形状的位置进行同样的变化
  132.                 var oldXY = _selectedShape.Rect.Location;
  133.                 oldXY.Offset(moveX, moveY);
  134.                 _selectedShape.Rect = new Rectangle(oldXY, _selectedShape.Rect.Size);
  135.                 //记录当前鼠标位置
  136.                 _lastMouseLocation.Offset(moveX, moveY);
  137.                 //重绘所有矩形
  138.                 DrawAllShape(panel1.CreateGraphics());
  139.             }
  140.         }
  141.         private void panel1_MouseUp(object sender, MouseEventArgs e)
  142.         {
  143.             //当鼠标松开时
  144.             if (_isMouseDown)
  145.             {
  146.                 //当且仅当:有鼠标按下且有矩形被选中时,才进行后续操作
  147.                 //重置相关记录信息
  148.                 _isMouseDown = false;
  149.                 _lastMouseLocation = Point.Empty;
  150.                 _selectedShape = null;
  151.             }
  152.         }
  153.     }
  154. }
复制代码
六、结语

绘制可拖动的矩形,是一切的开始和基础,我们通过本篇教程,了解到了如何一步步由浅入深,如何保持抽象的思想,为后续的开发打好基础。
本篇文章没有什么复杂的代码,都很常见,一个复杂的系统、繁复的功能都是由这样一个个简单的组件有机的组合成的。我们下篇教程,将会讲解如何在形状之间添加一条连线,这个也是流程图的基础。
感谢大家的观看,本人水平有限,文章不足之处欢迎大家评论指正。
-[END]-

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册