[原創]《C#高級GDI+實戰:從零開發一個流程圖》第02章:畫一個矩形,能拖動!
一、前言
就像開發的教程都從“Hello World!”開篇一樣,系列開始,我們也從一個最最簡單的功能開始:畫一個能拖動的矩形。
順便說一下,另一篇教程:(原創)[C#] GDI+ 之鼠標交互:原理、示例、一步步深入、性能優化 講的更詳細和深入,可以作為補充。
就讓我們從一個能拖動的矩形開始我們的流程圖開發之旅吧!
相信看完的你,一定會有所收獲!
本文地址:http://www.rzrgm.cn/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]-

浙公網安備 33010602011771號