一、前言

前面的课程我们实现了矩形、圆形的拖动,以及不同形状间的连线,在实现的过程中,很多读者都发现并提出来了存在显示质量差有锯齿、拖动不流畅还闪烁等问题,作为承上启下的一节课程,我们本节就来看一上如何解决这些问题。

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

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

二、先看效果

照例我们先来看一下实现效果:

第1个视频我们只需要看最后一部分,这部分对照了优化后的连线和形状显示质量:

第2个视频我们通过对照,看到了优化前后拖动流畅度及闪烁问题:

我们下面就来依次讲解。

三、实现效果1:优化锯齿等显示质量

这部分其实是很简单的,只需要设置GDI+的一些显示属性就行了,我们这里为了对照,直接将这些属性都设置为最高:

这些属性设置后,就实现了效果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 FormDemo03V3 : FormBase
{
public FormDemo03V3()
{
InitializeComponent();
DemoTitle = "第05节随课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>
public class EllipseShape
{
/// <summary>
/// 矩形ID
/// </summary>
public string Id { get; set; }
/// <summary>
/// 矩形位置和尺寸
/// </summary>
public Rectangle Rect { get; set; }
} /// <summary>
/// 直线连线定义
/// </summary>
public class LineLink
{
/// <summary>
/// 连线ID
/// </summary>
public string Id { get; set; }
/// <summary>
/// 开始形状是否是矩形
/// </summary>
public bool StartShapeIsRect { get; set; }
/// <summary>
/// 结束开关是否是矩形
/// </summary>
public bool EndShapeIsRect { get; set; }
/// <summary>
/// 连线开始形状ID
/// </summary>
public string StartShapeId { get; set; }
/// <summary>
/// 连线结束形状ID
/// </summary>
public string EndShapeId { get; set; }
} /// <summary>
/// 矩形集合
/// </summary>
List<RectShape> RectShapes = new List<RectShape>();
/// <summary>
/// 圆形集合
/// </summary>
List<EllipseShape> EllipseShapes = new List<EllipseShape>();
/// <summary>
/// 当前界面连线集合
/// </summary>
List<LineLink> Links = new List<LineLink>(); /// <summary>
/// 画一个矩形(不同颜色)
/// </summary>
/// <param name="g"></param>
/// <param name="shape"></param>
void DrawRectShape2(Graphics g, RectShape shape)
{
var index = RectShapes.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>
/// <param name="shape"></param>
void DrawEllipseShape2(Graphics g, EllipseShape shape)
{
var index = EllipseShapes.FindIndex(a => a.Id == shape.Id);
g.FillEllipse(GetBrush(index), shape.Rect);
g.DrawString(shape.Id, Font, Brushes.White, shape.Rect.X+20,shape.Rect.Y+20); //注:这里可以讲一下,要+20,是显示文本
} /// <summary>
/// 绘制一条连线(不同颜色)
/// </summary>
/// <param name="g"></param>
/// <param name="line"></param>
void DrawLine2(Graphics g, LineLink line)
{
//通过连线的开始形状ID和结束形状ID,计算两个形状的中心点坐标
var startPoint = line.StartShapeIsRect? GetCentertPointRect(line.StartShapeId): GetCentertPointEllipse(line.StartShapeId);
var endPoint =line.EndShapeIsRect? GetCentertPointRect(line.EndShapeId) : GetCentertPointEllipse(line.EndShapeId); var index = Links.FindIndex(a => a.Id == line.Id);
//绘制一条直线
g.DrawLine(GetPen(index), startPoint, endPoint);
} /// <summary>
/// 重新绘制当前所有矩形和连线
/// </summary>
/// <param name="g"></param>
void DrawAll(Graphics g)
{
//设置显示质量
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit; g.Clear(panel1.BackColor);
//绘制所有矩形
foreach (var sp in RectShapes)
{
DrawRectShape2(g, sp);
}
//绘制所有圆形
foreach (var sp in EllipseShapes)
{
DrawEllipseShape2(g, sp);
}
//绘制所有连线
foreach (var ln in Links)
{
DrawLine2(g, ln);
}
} //注:文章中说明;此处不过于抽象,后续章节会有 /// <summary>
/// 当前是否有鼠标按下,且有矩形被选中
/// </summary>
bool _isMouseDown = false;
/// <summary>
/// 是否是矩形被选中,不是则是圆形
/// </summary>
bool _isRectMouseDown = true;
/// <summary>
/// 最后一次鼠标的位置
/// </summary>
Point _lastMouseLocation = Point.Empty;
/// <summary>
/// 当前被鼠标选中的矩形
/// </summary>
RectShape _selectedRectShape = null;
/// <summary>
/// 当前被鼠标选中的圆形
/// </summary>
EllipseShape _selectedEllipseShape = null;
/// <summary>
/// 添加连线时选中的第一个是否是矩形,不是则是圆形
/// </summary>
bool _selectedStartIsRect = true;
/// <summary>
/// 添加连线时选中的第一个矩形
/// </summary>
RectShape _selectedStartRectShape = null;
/// <summary>
/// 添加连线时选中的第一个圆形
/// </summary>
EllipseShape _selectedStartEllipseShape = null;
/// <summary>
/// 添加连线时选中的第二个是否是矩形,不是则是圆形
/// </summary>
bool _selectedEndIsRect = true;
/// <summary>
/// 添加连线时选中的第二个矩形
/// </summary>
RectShape _selectedEndRectShape = null;
/// <summary>
/// 添加连线时选中的第二个圆形
/// </summary>
EllipseShape _selectedEndEllipseShape = null;
/// <summary>
/// 是否正添加连线
/// </summary>
bool _isAddLink = false; /// <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;
}
}
/// <summary>
/// 获取不同的画笔颜色
/// </summary>
/// <param name="i"></param>
/// <returns></returns>
Pen GetPen(int i)
{
return new Pen(GetBrush(i), 2);
} /// <summary>
/// 根据形状ID获取形状的中心点,以作为连线的起点或终点
/// </summary>
/// <param name="shapeId"></param>
/// <returns></returns>
Point GetCentertPointRect(string shapeId)
{
var sp = RectShapes.Find(a => a.Id == shapeId);
if (sp != null)
{
var line1X = sp.Rect.X + sp.Rect.Width / 2;
var line1Y = sp.Rect.Y + sp.Rect.Height / 2;
return new Point(line1X, line1Y);
}
return Point.Empty;
}
/// <summary>
/// 根据形状ID获取形状的中心点,以作为连线的起点或终点
/// </summary>
/// <param name="shapeId"></param>
/// <returns></returns>
Point GetCentertPointEllipse(string shapeId)
{
var sp = EllipseShapes.Find(a => a.Id == shapeId);
if (sp != null)
{
var line1X = sp.Rect.X + sp.Rect.Width / 2;
var line1Y = sp.Rect.Y + sp.Rect.Height / 2;
return new Point(line1X, line1Y);
}
return Point.Empty;
} private void toolStripButton1_Click(object sender, EventArgs e)
{
var rs = new RectShape()
{
Id = "矩形" + (RectShapes.Count + 1),
Rect = new Rectangle()
{
X = 50,
Y = 50,
Width = 100,
Height = 100,
},
};
RectShapes.Add(rs); //重绘所有矩形
DrawAll(panel1.CreateGraphics());
} private void panel1_MouseDown(object sender, MouseEventArgs e)
{
//当鼠标按下时 //取最上方的矩形,也就是最后添加的矩形
var sp = RectShapes.FindLast(a => a.Rect.Contains(e.Location));
//取最上方的圆形,也就是最后添加的圆形
var ep = EllipseShapes.FindLast(a => a.Rect.Contains(e.Location)); if (!_isAddLink)
{
//注:说明,这里是简化情况,因为是两个LIST,无法判断序号,就先判断矩形 //当前没有处理连线状态
if (sp != null)
{
//设置状态及选中矩形
_isMouseDown = true;
_lastMouseLocation = e.Location;
_selectedRectShape = sp;
_selectedEllipseShape = null;
_isRectMouseDown = true;
}
else if (ep != null)
{
//设置状态及选中圆形
_isMouseDown = true;
_lastMouseLocation = e.Location;
_selectedRectShape = null;
_selectedEllipseShape = ep;
_isRectMouseDown = false;
}
}
else
{
//正在添加连线 if (_selectedStartRectShape == null && _selectedStartEllipseShape == null)
{
//证明没有矩形和圆形被选中则设置开始形状
if (sp != null)
{
//设置开始形状是矩形
_selectedStartRectShape = sp;
_selectedStartEllipseShape = null;
_selectedStartIsRect = true;
}
else if (ep != null)
{
//设置开始形状是圆形
_selectedStartRectShape = null;
_selectedStartEllipseShape = ep;
_selectedStartIsRect = false;
}
toolStripStatusLabel1.Text = "请点击第2个形状";
}
else
{
//判断第2个形状是否是第1个形状
if (sp != null)
{
//证明当前选中的是矩形
if (_selectedStartRectShape != null)
{
//证明第1步选中的矩形 //判断当前选中的矩形是否是第1步选中的矩形
if (_selectedStartRectShape.Id == sp.Id)
{
toolStripStatusLabel1.Text = "不可选择同一个形状,请重新点击第2个形状";
return;
}
}
}
else if (ep != null)
{
//证明当前选中的圆形
if (_selectedStartEllipseShape != null)
{
//证明第1步选中的矩形 //判断当前选中的矩形是否是第1步选中的矩形
if (_selectedStartEllipseShape.Id == ep.Id)
{
toolStripStatusLabel1.Text = "不可选择同一个形状,请重新点击第2个形状";
return;
}
}
} //注:文章中说明:因为太过复杂,且不是本节重点,但不再进行去重判断 if (sp != null)
{
//设置结束形状是矩形
_selectedEndRectShape = sp;
_selectedEndEllipseShape = null;
_selectedEndIsRect = true;
}
else if (ep != null)
{
//设置结束形状是圆形
_selectedEndRectShape = null;
_selectedEndEllipseShape = ep;
_selectedEndIsRect = false;
}
else
{
return;
} //两个形状都设置了,便添加一条新连线
Links.Add(new LineLink()
{
Id = "连线" + (Links.Count + 1),
StartShapeId =_selectedStartIsRect? _selectedStartRectShape.Id:_selectedStartEllipseShape.Id,
EndShapeId =_selectedEndIsRect? _selectedEndRectShape.Id:_selectedEndEllipseShape.Id,
StartShapeIsRect=_selectedStartIsRect,
EndShapeIsRect=_selectedEndIsRect,
});
//两个形状都已选择,结束添加连线状态
_isAddLink = false;
toolStripStatusLabel1.Text = "";
//重绘以显示连线
DrawAll(panel1.CreateGraphics()); } } } private void panel1_MouseMove(object sender, MouseEventArgs e)
{
//当鼠标移动时 //如果处于添加连线时,则不移动形状
if (_isAddLink) return; if (_isMouseDown)
{
//当且仅当:有鼠标按下且有矩形被选中时,才进行后续操作 //改变选中矩形的位置信息,随着鼠标移动而移动 //计算鼠标位置变化信息
var moveX = e.Location.X - _lastMouseLocation.X;
var moveY = e.Location.Y - _lastMouseLocation.Y; //将选中形状的位置进行同样的变化
if (_isRectMouseDown)
{
var oldXY = _selectedRectShape.Rect.Location;
oldXY.Offset(moveX, moveY);
_selectedRectShape.Rect = new Rectangle(oldXY, _selectedRectShape.Rect.Size); }
else
{
var oldXY = _selectedEllipseShape.Rect.Location;
oldXY.Offset(moveX, moveY);
_selectedEllipseShape.Rect = new Rectangle(oldXY, _selectedEllipseShape.Rect.Size); } //记录当前鼠标位置
_lastMouseLocation.Offset(moveX, moveY); //重绘所有矩形
DrawAll(panel1.CreateGraphics());
} } private void panel1_MouseUp(object sender, MouseEventArgs e)
{
//当鼠标松开时
if (_isMouseDown)
{
//当且仅当:有鼠标按下且有矩形被选中时,才进行后续操作 //重置相关记录信息
_isMouseDown = false;
_lastMouseLocation = Point.Empty;
_selectedRectShape = null;
_selectedEllipseShape = null;
}
} private void toolStripButton2_Click(object sender, EventArgs e)
{
_isAddLink = true;
_selectedStartRectShape = null;
_selectedEndRectShape = null;
_selectedStartEllipseShape = null;
_selectedEndEllipseShape = null;
toolStripStatusLabel1.Text = "请点击第1个形状";
} private void toolStripButton3_Click(object sender, EventArgs e)
{
_isAddLink = false;
_selectedStartRectShape = null;
_selectedEndRectShape = null;
toolStripStatusLabel1.Text = "";
DrawAll(panel1.CreateGraphics());
} private void toolStripButton4_Click(object sender, EventArgs e)
{
var rs = new EllipseShape()
{
Id = "圆形" + (EllipseShapes.Count + 1),
Rect = new Rectangle()
{
X = 50,
Y = 50,
Width = 100,
Height = 100,
},
};
EllipseShapes.Add(rs); //重绘所有矩形
DrawAll(panel1.CreateGraphics());
}
} }

四、实现效果2:解决拖动慢、闪烁等问题

本来未设置显示质量时,拖动形状时就会闪烁,而经过上面的设置后,拖动变慢了,闪烁更严重了。

闪烁是因为我们每次拖动都是整个控件清空再依次绘制所有形状和连线,这些耗时就会导致闪烁问题。而在设置为高质量显示后,绘制更慢,闪烁便会更明显,拖动起来也感觉不跟手变慢了。

我们下面就来解决一下这个问题。

注:更详细深入的图文讲解在这个教程里有说明,不再赘述:https://www.cnblogs.com/lesliexin/p/16554752.html

1,开启双缓冲

可以说遇事不决,先开双缓冲,如图:

当然,仅仅开启双缓冲效果并不大,还要搭配下面的处理才行。

2,使用内存绘图

内存绘图,没什么神秘的,就是将所有形状、连线绘制在一个位图(Bitmap)对象上而已,当绘制完成后,再将此位图绘制到控件上,以提高绘制效率,达到去除闪烁的问题。

如上所示,我们先创建一个与控件尺寸一样大的位图对象,然后在此位图上绘制所有连线,最后绘制到控件上。

好了,到此我们就实现了效果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 FormDemo04V1 : FormBase
{
public FormDemo04V1()
{
InitializeComponent();
DemoTitle = "第06节随课Demo Part1";
DemoNote = "效果:优化拖动慢、拖动时闪烁等问题"; SetStyle(ControlStyles.AllPaintingInWmPaint |
ControlStyles.UserPaint |
ControlStyles.OptimizedDoubleBuffer,true);
} /// <summary>
/// 矩形定义
/// </summary>
public class RectShape
{
/// <summary>
/// 矩形ID
/// </summary>
public string Id { get; set; }
/// <summary>
/// 矩形位置和尺寸
/// </summary>
public Rectangle Rect { get; set; }
} /// <summary>
/// 圆形定义
/// </summary>
public class EllipseShape
{
/// <summary>
/// 矩形ID
/// </summary>
public string Id { get; set; }
/// <summary>
/// 矩形位置和尺寸
/// </summary>
public Rectangle Rect { get; set; }
} /// <summary>
/// 直线连线定义
/// </summary>
public class LineLink
{
/// <summary>
/// 连线ID
/// </summary>
public string Id { get; set; }
/// <summary>
/// 开始形状是否是矩形
/// </summary>
public bool StartShapeIsRect { get; set; }
/// <summary>
/// 结束开关是否是矩形
/// </summary>
public bool EndShapeIsRect { get; set; }
/// <summary>
/// 连线开始形状ID
/// </summary>
public string StartShapeId { get; set; }
/// <summary>
/// 连线结束形状ID
/// </summary>
public string EndShapeId { get; set; }
} /// <summary>
/// 矩形集合
/// </summary>
List<RectShape> RectShapes = new List<RectShape>();
/// <summary>
/// 圆形集合
/// </summary>
List<EllipseShape> EllipseShapes = new List<EllipseShape>();
/// <summary>
/// 当前界面连线集合
/// </summary>
List<LineLink> Links = new List<LineLink>(); /// <summary>
/// 画一个矩形(不同颜色)
/// </summary>
/// <param name="g"></param>
/// <param name="shape"></param>
void DrawRectShape2(Graphics g, RectShape shape)
{
var index = RectShapes.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>
/// <param name="shape"></param>
void DrawEllipseShape2(Graphics g, EllipseShape shape)
{
var index = EllipseShapes.FindIndex(a => a.Id == shape.Id);
g.FillEllipse(GetBrush(index), shape.Rect);
g.DrawString(shape.Id, Font, Brushes.White, shape.Rect.X+20,shape.Rect.Y+20); //注:这里可以讲一下,要+20,是显示文本
} /// <summary>
/// 绘制一条连线(不同颜色)
/// </summary>
/// <param name="g"></param>
/// <param name="line"></param>
void DrawLine2(Graphics g, LineLink line)
{
//通过连线的开始形状ID和结束形状ID,计算两个形状的中心点坐标
var startPoint = line.StartShapeIsRect? GetCentertPointRect(line.StartShapeId): GetCentertPointEllipse(line.StartShapeId);
var endPoint =line.EndShapeIsRect? GetCentertPointRect(line.EndShapeId) : GetCentertPointEllipse(line.EndShapeId); var index = Links.FindIndex(a => a.Id == line.Id);
//绘制一条直线
g.DrawLine(GetPen(index), startPoint, endPoint);
} //注:文章中说明,参见下面的地址,讲的很清楚。 Bitmap _bmp; /// <summary>
/// 重新绘制当前所有矩形和连线
/// </summary>
/// <param name="g"></param>
void DrawAll(Graphics g1)
{
//创建内存绘图,将形状和连线绘制到此内存绘图上,然后再一次性绘制到控件上
_bmp = new Bitmap(panel1.Width, panel1.Height);
var g = Graphics.FromImage(_bmp); //设置显示质量
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit; g.Clear(panel1.BackColor);
//绘制所有矩形
foreach (var sp in RectShapes)
{
DrawRectShape2(g, sp);
}
//绘制所有圆形
foreach (var sp in EllipseShapes)
{
DrawEllipseShape2(g, sp);
}
//绘制所有连线
foreach (var ln in Links)
{
DrawLine2(g, ln);
} //绘制内存绘图到控件上
g1.DrawImage(_bmp, new PointF(0, 0));
} //注:文章中说明;此处不过于抽象,后续章节会有 /// <summary>
/// 当前是否有鼠标按下,且有矩形被选中
/// </summary>
bool _isMouseDown = false;
/// <summary>
/// 是否是矩形被选中,不是则是圆形
/// </summary>
bool _isRectMouseDown = true;
/// <summary>
/// 最后一次鼠标的位置
/// </summary>
Point _lastMouseLocation = Point.Empty;
/// <summary>
/// 当前被鼠标选中的矩形
/// </summary>
RectShape _selectedRectShape = null;
/// <summary>
/// 当前被鼠标选中的圆形
/// </summary>
EllipseShape _selectedEllipseShape = null;
/// <summary>
/// 添加连线时选中的第一个是否是矩形,不是则是圆形
/// </summary>
bool _selectedStartIsRect = true;
/// <summary>
/// 添加连线时选中的第一个矩形
/// </summary>
RectShape _selectedStartRectShape = null;
/// <summary>
/// 添加连线时选中的第一个圆形
/// </summary>
EllipseShape _selectedStartEllipseShape = null;
/// <summary>
/// 添加连线时选中的第二个是否是矩形,不是则是圆形
/// </summary>
bool _selectedEndIsRect = true;
/// <summary>
/// 添加连线时选中的第二个矩形
/// </summary>
RectShape _selectedEndRectShape = null;
/// <summary>
/// 添加连线时选中的第二个圆形
/// </summary>
EllipseShape _selectedEndEllipseShape = null;
/// <summary>
/// 是否正添加连线
/// </summary>
bool _isAddLink = false; /// <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;
}
}
/// <summary>
/// 获取不同的画笔颜色
/// </summary>
/// <param name="i"></param>
/// <returns></returns>
Pen GetPen(int i)
{
return new Pen(GetBrush(i), 2);
} /// <summary>
/// 根据形状ID获取形状的中心点,以作为连线的起点或终点
/// </summary>
/// <param name="shapeId"></param>
/// <returns></returns>
Point GetCentertPointRect(string shapeId)
{
var sp = RectShapes.Find(a => a.Id == shapeId);
if (sp != null)
{
var line1X = sp.Rect.X + sp.Rect.Width / 2;
var line1Y = sp.Rect.Y + sp.Rect.Height / 2;
return new Point(line1X, line1Y);
}
return Point.Empty;
}
/// <summary>
/// 根据形状ID获取形状的中心点,以作为连线的起点或终点
/// </summary>
/// <param name="shapeId"></param>
/// <returns></returns>
Point GetCentertPointEllipse(string shapeId)
{
var sp = EllipseShapes.Find(a => a.Id == shapeId);
if (sp != null)
{
var line1X = sp.Rect.X + sp.Rect.Width / 2;
var line1Y = sp.Rect.Y + sp.Rect.Height / 2;
return new Point(line1X, line1Y);
}
return Point.Empty;
} private void toolStripButton1_Click(object sender, EventArgs e)
{
var rs = new RectShape()
{
Id = "矩形" + (RectShapes.Count + 1),
Rect = new Rectangle()
{
X = 50,
Y = 50,
Width = 100,
Height = 100,
},
};
RectShapes.Add(rs); //重绘所有矩形
DrawAll(panel1.CreateGraphics());
} private void panel1_MouseDown(object sender, MouseEventArgs e)
{
//当鼠标按下时 //取最上方的矩形,也就是最后添加的矩形
var sp = RectShapes.FindLast(a => a.Rect.Contains(e.Location));
//取最上方的圆形,也就是最后添加的圆形
var ep = EllipseShapes.FindLast(a => a.Rect.Contains(e.Location)); if (!_isAddLink)
{
//注:说明,这里是简化情况,因为是两个LIST,无法判断序号,就先判断矩形 //当前没有处理连线状态
if (sp != null)
{
//设置状态及选中矩形
_isMouseDown = true;
_lastMouseLocation = e.Location;
_selectedRectShape = sp;
_selectedEllipseShape = null;
_isRectMouseDown = true;
}
else if (ep != null)
{
//设置状态及选中圆形
_isMouseDown = true;
_lastMouseLocation = e.Location;
_selectedRectShape = null;
_selectedEllipseShape = ep;
_isRectMouseDown = false;
}
}
else
{
//正在添加连线 if (_selectedStartRectShape == null && _selectedStartEllipseShape == null)
{
//证明没有矩形和圆形被选中则设置开始形状
if (sp != null)
{
//设置开始形状是矩形
_selectedStartRectShape = sp;
_selectedStartEllipseShape = null;
_selectedStartIsRect = true;
}
else if (ep != null)
{
//设置开始形状是圆形
_selectedStartRectShape = null;
_selectedStartEllipseShape = ep;
_selectedStartIsRect = false;
}
toolStripStatusLabel1.Text = "请点击第2个形状";
}
else
{
//判断第2个形状是否是第1个形状
if (sp != null)
{
//证明当前选中的是矩形
if (_selectedStartRectShape != null)
{
//证明第1步选中的矩形 //判断当前选中的矩形是否是第1步选中的矩形
if (_selectedStartRectShape.Id == sp.Id)
{
toolStripStatusLabel1.Text = "不可选择同一个形状,请重新点击第2个形状";
return;
}
}
}
else if (ep != null)
{
//证明当前选中的圆形
if (_selectedStartEllipseShape != null)
{
//证明第1步选中的矩形 //判断当前选中的矩形是否是第1步选中的矩形
if (_selectedStartEllipseShape.Id == ep.Id)
{
toolStripStatusLabel1.Text = "不可选择同一个形状,请重新点击第2个形状";
return;
}
}
} //注:文章中说明:因为太过复杂,且不是本节重点,但不再进行去重判断 if (sp != null)
{
//设置结束形状是矩形
_selectedEndRectShape = sp;
_selectedEndEllipseShape = null;
_selectedEndIsRect = true;
}
else if (ep != null)
{
//设置结束形状是圆形
_selectedEndRectShape = null;
_selectedEndEllipseShape = ep;
_selectedEndIsRect = false;
}
else
{
return;
} //两个形状都设置了,便添加一条新连线
Links.Add(new LineLink()
{
Id = "连线" + (Links.Count + 1),
StartShapeId =_selectedStartIsRect? _selectedStartRectShape.Id:_selectedStartEllipseShape.Id,
EndShapeId =_selectedEndIsRect? _selectedEndRectShape.Id:_selectedEndEllipseShape.Id,
StartShapeIsRect=_selectedStartIsRect,
EndShapeIsRect=_selectedEndIsRect,
});
//两个形状都已选择,结束添加连线状态
_isAddLink = false;
toolStripStatusLabel1.Text = "";
//重绘以显示连线
DrawAll(panel1.CreateGraphics()); } } } private void panel1_MouseMove(object sender, MouseEventArgs e)
{
//当鼠标移动时 //如果处于添加连线时,则不移动形状
if (_isAddLink) return; if (_isMouseDown)
{
//当且仅当:有鼠标按下且有矩形被选中时,才进行后续操作 //改变选中矩形的位置信息,随着鼠标移动而移动 //计算鼠标位置变化信息
var moveX = e.Location.X - _lastMouseLocation.X;
var moveY = e.Location.Y - _lastMouseLocation.Y; //将选中形状的位置进行同样的变化
if (_isRectMouseDown)
{
var oldXY = _selectedRectShape.Rect.Location;
oldXY.Offset(moveX, moveY);
_selectedRectShape.Rect = new Rectangle(oldXY, _selectedRectShape.Rect.Size); }
else
{
var oldXY = _selectedEllipseShape.Rect.Location;
oldXY.Offset(moveX, moveY);
_selectedEllipseShape.Rect = new Rectangle(oldXY, _selectedEllipseShape.Rect.Size); } //记录当前鼠标位置
_lastMouseLocation.Offset(moveX, moveY); //重绘所有矩形
DrawAll(panel1.CreateGraphics());
} } private void panel1_MouseUp(object sender, MouseEventArgs e)
{
//当鼠标松开时
if (_isMouseDown)
{
//当且仅当:有鼠标按下且有矩形被选中时,才进行后续操作 //重置相关记录信息
_isMouseDown = false;
_lastMouseLocation = Point.Empty;
_selectedRectShape = null;
_selectedEllipseShape = null;
}
} private void toolStripButton2_Click(object sender, EventArgs e)
{
_isAddLink = true;
_selectedStartRectShape = null;
_selectedEndRectShape = null;
_selectedStartEllipseShape = null;
_selectedEndEllipseShape = null;
toolStripStatusLabel1.Text = "请点击第1个形状";
} private void toolStripButton3_Click(object sender, EventArgs e)
{
_isAddLink = false;
_selectedStartRectShape = null;
_selectedEndRectShape = null;
toolStripStatusLabel1.Text = "";
DrawAll(panel1.CreateGraphics());
} private void toolStripButton4_Click(object sender, EventArgs e)
{
var rs = new EllipseShape()
{
Id = "圆形" + (EllipseShapes.Count + 1),
Rect = new Rectangle()
{
X = 50,
Y = 50,
Width = 100,
Height = 100,
},
};
EllipseShapes.Add(rs); //重绘所有矩形
DrawAll(panel1.CreateGraphics());
}
} }

五、结语

本节课程很简单,是基于当前为止的需要,简单的解决了显示质量和拖动闪烁的问题,当然上面的代码并不非常完善,像资源释放、全屏显示时仍可能出现拖动不跟手等问题都没作处理,这些不是本节重点,我们会在后面的课程里一一解决。

下节课程我们就来对矩形、圆形等形状和连线做一个抽象,以解决前面课程遇到的增加一个新的形状时代码就要添加好多额外判断代码的问题,以及为后续的支持菱形、平行四边形等任意形状做基础。当然连线也是,后面不止有直线,还有贝塞尔曲线、正交连线等各种连线。

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

-[END]-

[原创]《C#高级GDI+实战:从零开发一个流程图》第05章:有锯齿?拖动闪烁?优化!优化!的更多相关文章

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

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

  2. 【Android开发VR实战】三.开发一个寻宝类VR游戏TreasureHunt

    转载请注明出处:http://blog.csdn.net/linglongxin24/article/details/53939303 本文出自[DylanAndroid的博客] [Android开发 ...

  3. 【Nodejs】326- 从零开发一个node命令行工具

    本文由 IMWeb 社区授权转载自腾讯内部 KM 论坛.点击阅读原文查看 IMWeb 社区更多精彩文章. 什么是命令行工具? 命令行工具(Cmmand Line Interface)简称cli,顾名思 ...

  4. Django实战总结 - 快速开发一个数据库查询工具

    一.简介 Django 是一个开放源代码的 Web 应用框架,由 Python 写成. Django 只要很少的代码就可以轻松地完成一个正式网站所需要的大部分内容,并进一步开发出全功能的 Web 服务 ...

  5. 如何从零开发一个NuGet软件包?

    作者:依乐祝 首发地址:https://www.cnblogs.com/yilezhu/p/14175019.html 我想目前每个.net开发人员都应该知道nuget.org和NuGet软件包吧.但 ...

  6. 【前端vue进阶实战】:从零打造一个流程图、拓扑图项目【Nuxt.js + Element + Vuex】 (一)

    本系列教程是用Vue.js + Nuxt.js + Element + Vuex + 开源js绘图库,打造一个属于自己的在线绘图软件,最终效果:topology.le5le.com .如果你觉得好,欢 ...

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

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

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

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

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

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

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

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

随机推荐

  1. JMeter跨线程传参总结

  2. rabbitmq学习与总结

    一.rabbitmq的使用场景 1.高并发的流量削峰 举个例子,假设某订单系统每秒最多能处理一万次订单,也就是最多承受的10000qps,这个处理能力应付正常时段的下单时绰绰有余,正常时段我们下单一秒 ...

  3. File类使用详解

    File类是java io包下代表与平台无关的文件和目录,也就是说,在程序中操作文件和目录都可以通过File类来完成.但是File不能访问文件内容本身,访问文件内容需要使用输入/输出流. File类的 ...

  4. apisix~ApisixPluginConfig的使用

    1. ApisixPluginConfig 的作用 插件配置复用:将插件配置定义为独立的资源,供多个路由或服务引用. 解耦插件与路由:修改插件配置时,只需更新 ApisixPluginConfig,无 ...

  5. CentOS7搭建XSS平台

    我的服务器是CentOS7.8 1.安装php 7 CentOS7的默认PHP版本是PHP5,但是如果我们要安装PHP7,不需要将现有的PHP5删除,只要将PHP升级到PHP7即可. 使用 yum p ...

  6. net core mvc6使用jwt实现简单的登录

    创建一个新的.NET Core Web应用程序项目.在创建项目时,选择MVC模板. 在项目中添加Microsoft.AspNetCore.Authentication.JwtBearer包: 在Vis ...

  7. SpringBoot2 可以使用 SolonMCP 开发 MCP(江湖救急)

    MCP 官方的 java-sdk 目前要求 java17+(直接使用 sdk 也比较复杂).Spring-AI(有 MCP 内容)也是要求 java17+. SpringBoot2 怎么办? 使用 S ...

  8. TVM中的Compute操作

    定义 TVM从Halide继承了计算与调度分离的思想,并在其内部重用了部分Halide的调度原语,也引入了一些新的调度原语,用于优化GPU和专用加速器性能. 先举个例子吧: import tvm fr ...

  9. Pytorch 看起来好像没占gpu的样子的原因

    今天好哥们儿赞助的3080到手了,欣喜若狂的装上 然后跑了跑MNIST,看着任务管理器CPU跑100%,GPU跑3%,查了半天解决不了,郁闷了好一会儿.. 后来在https://www.bilibil ...

  10. AI 极客低代码平台快速上手 --生成Python代码

    1.侧栏菜单选择"Python": 点击代码区右上角的"代码魔法棒"图标,在弹出的确认面板中点击"确认"按钮,一秒生成基于FastApi最新 ...