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


一、前言

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

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

本文地址: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]-