[原创]《C#高级GDI+实战:从零开发一个流程图》第05章:有锯齿?拖动闪烁?优化!优化! [复制链接]
发表于 2025-7-16 11:18:34 | 显示全部楼层 |阅读模式
一、前言

前面的课程我们实现了矩形、圆形的拖动,以及不同外形间的连线,在实现的过程中,很多读者都发现并提出来了存在显示质量差有锯齿、拖动不流畅还闪烁等题目,作为承上启下的一节课程,我们本节就来看一上如何解决这些题目。
相信看完的你,一定会有所收获!
本文地点:https://www.cnblogs.com/lesliexin/p/18930941
二、先看效果

照例我们先来看一下实现效果:
第1个视频我们只必要看末了一部分,这部分对照了优化后的连线和外形显示质量:

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

我们下面就来依次解说。
三、实现效果1:优化锯齿等显示质量

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

这些属性设置后,就实现了效果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 FormDemo03V3 : FormBase
  13.     {
  14.         public FormDemo03V3()
  15.         {
  16.             InitializeComponent();
  17.             DemoTitle = "第05节随课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.         public class EllipseShape
  38.         {
  39.             /// <summary>
  40.             /// 矩形ID
  41.             /// </summary>
  42.             public string Id { get; set; }
  43.             /// <summary>
  44.             /// 矩形位置和尺寸
  45.             /// </summary>
  46.             public Rectangle Rect { get; set; }
  47.         }
  48.         /// <summary>
  49.         /// 直线连线定义
  50.         /// </summary>
  51.         public class LineLink
  52.         {
  53.             /// <summary>
  54.             /// 连线ID
  55.             /// </summary>
  56.             public string Id { get; set; }
  57.             /// <summary>
  58.             /// 开始形状是否是矩形
  59.             /// </summary>
  60.             public bool StartShapeIsRect { get; set; }
  61.             /// <summary>
  62.             /// 结束开关是否是矩形
  63.             /// </summary>
  64.             public bool EndShapeIsRect { get; set; }
  65.             /// <summary>
  66.             /// 连线开始形状ID
  67.             /// </summary>
  68.             public string StartShapeId { get; set; }
  69.             /// <summary>
  70.             /// 连线结束形状ID
  71.             /// </summary>
  72.             public string EndShapeId { get; set; }
  73.         }
  74.         /// <summary>
  75.         /// 矩形集合
  76.         /// </summary>
  77.         List<RectShape> RectShapes = new List<RectShape>();
  78.         /// <summary>
  79.         /// 圆形集合
  80.         /// </summary>
  81.         List<EllipseShape> EllipseShapes = new List<EllipseShape>();
  82.         /// <summary>
  83.         /// 当前界面连线集合
  84.         /// </summary>
  85.         List<LineLink> Links = new List<LineLink>();
  86.         /// <summary>
  87.         /// 画一个矩形(不同颜色)
  88.         /// </summary>
  89.         /// <param name="g"></param>
  90.         /// <param name="shape"></param>
  91.         void DrawRectShape2(Graphics g, RectShape shape)
  92.         {
  93.             var index = RectShapes.FindIndex(a => a.Id == shape.Id);
  94.             g.FillRectangle(GetBrush(index), shape.Rect);
  95.             g.DrawString(shape.Id, Font, Brushes.White, shape.Rect);
  96.         }
  97.         /// <summary>
  98.         /// 画一个圆形(不同颜色)
  99.         /// </summary>
  100.         /// <param name="g"></param>
  101.         /// <param name="shape"></param>
  102.         void DrawEllipseShape2(Graphics g, EllipseShape shape)
  103.         {
  104.             var index = EllipseShapes.FindIndex(a => a.Id == shape.Id);
  105.             g.FillEllipse(GetBrush(index), shape.Rect);
  106.             g.DrawString(shape.Id, Font, Brushes.White, shape.Rect.X+20,shape.Rect.Y+20);            //注:这里可以讲一下,要+20,是显示文本
  107.         }
  108.         /// <summary>
  109.         /// 绘制一条连线(不同颜色)
  110.         /// </summary>
  111.         /// <param name="g"></param>
  112.         /// <param name="line"></param>
  113.         void DrawLine2(Graphics g, LineLink line)
  114.         {
  115.             //通过连线的开始形状ID和结束形状ID,计算两个形状的中心点坐标
  116.             var startPoint = line.StartShapeIsRect? GetCentertPointRect(line.StartShapeId): GetCentertPointEllipse(line.StartShapeId);
  117.             var endPoint =line.EndShapeIsRect? GetCentertPointRect(line.EndShapeId) : GetCentertPointEllipse(line.EndShapeId);
  118.             var index = Links.FindIndex(a => a.Id == line.Id);
  119.             //绘制一条直线
  120.             g.DrawLine(GetPen(index), startPoint, endPoint);
  121.         }
  122.         /// <summary>
  123.         /// 重新绘制当前所有矩形和连线
  124.         /// </summary>
  125.         /// <param name="g"></param>
  126.         void DrawAll(Graphics g)
  127.         {
  128.             //设置显示质量
  129.             g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
  130.             g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
  131.             g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
  132.             g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
  133.             g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
  134.             g.Clear(panel1.BackColor);
  135.             //绘制所有矩形
  136.             foreach (var sp in RectShapes)
  137.             {
  138.                 DrawRectShape2(g, sp);
  139.             }
  140.             //绘制所有圆形
  141.             foreach (var sp in EllipseShapes)
  142.             {
  143.                 DrawEllipseShape2(g, sp);
  144.             }
  145.             //绘制所有连线
  146.             foreach (var ln in Links)
  147.             {
  148.                 DrawLine2(g, ln);
  149.             }
  150.         }
  151.         //注:文章中说明;此处不过于抽象,后续章节会有
  152.         /// <summary>
  153.         /// 当前是否有鼠标按下,且有矩形被选中
  154.         /// </summary>
  155.         bool _isMouseDown = false;
  156.         /// <summary>
  157.         /// 是否是矩形被选中,不是则是圆形
  158.         /// </summary>
  159.         bool _isRectMouseDown = true;
  160.         /// <summary>
  161.         /// 最后一次鼠标的位置
  162.         /// </summary>
  163.         Point _lastMouseLocation = Point.Empty;
  164.         /// <summary>
  165.         /// 当前被鼠标选中的矩形
  166.         /// </summary>
  167.         RectShape _selectedRectShape = null;
  168.         /// <summary>
  169.         /// 当前被鼠标选中的圆形
  170.         /// </summary>
  171.         EllipseShape _selectedEllipseShape = null;
  172.         /// <summary>
  173.         /// 添加连线时选中的第一个是否是矩形,不是则是圆形
  174.         /// </summary>
  175.         bool _selectedStartIsRect = true;
  176.         /// <summary>
  177.         /// 添加连线时选中的第一个矩形
  178.         /// </summary>
  179.         RectShape _selectedStartRectShape = null;
  180.         /// <summary>
  181.         /// 添加连线时选中的第一个圆形
  182.         /// </summary>
  183.         EllipseShape _selectedStartEllipseShape = null;
  184.         /// <summary>
  185.         /// 添加连线时选中的第二个是否是矩形,不是则是圆形
  186.         /// </summary>
  187.         bool _selectedEndIsRect = true;
  188.         /// <summary>
  189.         /// 添加连线时选中的第二个矩形
  190.         /// </summary>
  191.         RectShape _selectedEndRectShape = null;
  192.         /// <summary>
  193.         /// 添加连线时选中的第二个圆形
  194.         /// </summary>
  195.         EllipseShape _selectedEndEllipseShape = null;
  196.         /// <summary>
  197.         /// 是否正添加连线
  198.         /// </summary>
  199.         bool _isAddLink = false;
  200.         /// <summary>
  201.         /// 获取不同的背景颜色
  202.         /// </summary>
  203.         /// <param name="i"></param>
  204.         /// <returns></returns>
  205.         Brush GetBrush(int i)
  206.         {
  207.             switch (i)
  208.             {
  209.                 case 0: return Brushes.Red;
  210.                 case 1: return Brushes.Green;
  211.                 case 2: return Brushes.Blue;
  212.                 case 3: return Brushes.Orange;
  213.                 case 4: return Brushes.Purple;
  214.                 default: return Brushes.Red;
  215.             }
  216.         }
  217.         /// <summary>
  218.         /// 获取不同的画笔颜色
  219.         /// </summary>
  220.         /// <param name="i"></param>
  221.         /// <returns></returns>
  222.         Pen GetPen(int i)
  223.         {
  224.             return new Pen(GetBrush(i), 2);
  225.         }
  226.         /// <summary>
  227.         /// 根据形状ID获取形状的中心点,以作为连线的起点或终点
  228.         /// </summary>
  229.         /// <param name="shapeId"></param>
  230.         /// <returns></returns>
  231.         Point GetCentertPointRect(string shapeId)
  232.         {
  233.             var sp = RectShapes.Find(a => a.Id == shapeId);
  234.             if (sp != null)
  235.             {
  236.                 var line1X = sp.Rect.X + sp.Rect.Width / 2;
  237.                 var line1Y = sp.Rect.Y + sp.Rect.Height / 2;
  238.                 return new Point(line1X, line1Y);
  239.             }
  240.             return Point.Empty;
  241.         }
  242.         /// <summary>
  243.         /// 根据形状ID获取形状的中心点,以作为连线的起点或终点
  244.         /// </summary>
  245.         /// <param name="shapeId"></param>
  246.         /// <returns></returns>
  247.         Point GetCentertPointEllipse(string shapeId)
  248.         {
  249.             var sp = EllipseShapes.Find(a => a.Id == shapeId);
  250.             if (sp != null)
  251.             {
  252.                 var line1X = sp.Rect.X + sp.Rect.Width / 2;
  253.                 var line1Y = sp.Rect.Y + sp.Rect.Height / 2;
  254.                 return new Point(line1X, line1Y);
  255.             }
  256.             return Point.Empty;
  257.         }
  258.         private void toolStripButton1_Click(object sender, EventArgs e)
  259.         {
  260.             var rs = new RectShape()
  261.             {
  262.                 Id = "矩形" + (RectShapes.Count + 1),
  263.                 Rect = new Rectangle()
  264.                 {
  265.                     X = 50,
  266.                     Y = 50,
  267.                     Width = 100,
  268.                     Height = 100,
  269.                 },
  270.             };
  271.             RectShapes.Add(rs);
  272.             //重绘所有矩形
  273.             DrawAll(panel1.CreateGraphics());
  274.         }
  275.         private void panel1_MouseDown(object sender, MouseEventArgs e)
  276.         {
  277.             //当鼠标按下时
  278.             //取最上方的矩形,也就是最后添加的矩形
  279.             var sp = RectShapes.FindLast(a => a.Rect.Contains(e.Location));
  280.             //取最上方的圆形,也就是最后添加的圆形
  281.             var ep = EllipseShapes.FindLast(a => a.Rect.Contains(e.Location));
  282.             if (!_isAddLink)
  283.             {
  284.                 //注:说明,这里是简化情况,因为是两个LIST,无法判断序号,就先判断矩形
  285.                
  286.                 //当前没有处理连线状态
  287.                 if (sp != null)
  288.                 {
  289.                     //设置状态及选中矩形
  290.                     _isMouseDown = true;
  291.                     _lastMouseLocation = e.Location;
  292.                     _selectedRectShape = sp;
  293.                     _selectedEllipseShape = null;
  294.                     _isRectMouseDown = true;
  295.                 }
  296.                 else if (ep != null)
  297.                 {
  298.                     //设置状态及选中圆形
  299.                     _isMouseDown = true;
  300.                     _lastMouseLocation = e.Location;
  301.                     _selectedRectShape = null;
  302.                     _selectedEllipseShape = ep;
  303.                     _isRectMouseDown = false;
  304.                 }
  305.             }
  306.             else
  307.             {
  308.                 //正在添加连线
  309.                 if (_selectedStartRectShape == null && _selectedStartEllipseShape == null)
  310.                 {
  311.                     //证明没有矩形和圆形被选中则设置开始形状
  312.                     if (sp != null)
  313.                     {
  314.                         //设置开始形状是矩形
  315.                         _selectedStartRectShape = sp;
  316.                         _selectedStartEllipseShape = null;
  317.                         _selectedStartIsRect = true;
  318.                     }
  319.                     else if (ep != null)
  320.                     {
  321.                         //设置开始形状是圆形
  322.                         _selectedStartRectShape = null;
  323.                         _selectedStartEllipseShape = ep;
  324.                         _selectedStartIsRect = false;
  325.                     }
  326.                     toolStripStatusLabel1.Text = "请点击第2个形状";
  327.                 }
  328.                 else
  329.                 {
  330.                     //判断第2个形状是否是第1个形状
  331.                     if (sp != null)
  332.                     {
  333.                         //证明当前选中的是矩形
  334.                         if (_selectedStartRectShape != null)
  335.                         {
  336.                             //证明第1步选中的矩形
  337.                             //判断当前选中的矩形是否是第1步选中的矩形
  338.                             if (_selectedStartRectShape.Id == sp.Id)
  339.                             {
  340.                                 toolStripStatusLabel1.Text = "不可选择同一个形状,请重新点击第2个形状";
  341.                                 return;
  342.                             }
  343.                         }
  344.                     }
  345.                     else if (ep != null)
  346.                     {
  347.                         //证明当前选中的圆形
  348.                         if (_selectedStartEllipseShape != null)
  349.                         {
  350.                             //证明第1步选中的矩形
  351.                             //判断当前选中的矩形是否是第1步选中的矩形
  352.                             if (_selectedStartEllipseShape.Id == ep.Id)
  353.                             {
  354.                                 toolStripStatusLabel1.Text = "不可选择同一个形状,请重新点击第2个形状";
  355.                                 return;
  356.                             }
  357.                         }
  358.                     }
  359.                     //注:文章中说明:因为太过复杂,且不是本节重点,但不再进行去重判断
  360.                     if (sp != null)
  361.                     {
  362.                         //设置结束形状是矩形
  363.                         _selectedEndRectShape = sp;
  364.                         _selectedEndEllipseShape = null;
  365.                         _selectedEndIsRect = true;
  366.                     }
  367.                     else if (ep != null)
  368.                     {
  369.                         //设置结束形状是圆形
  370.                         _selectedEndRectShape = null;
  371.                         _selectedEndEllipseShape = ep;
  372.                         _selectedEndIsRect = false;
  373.                     }
  374.                     else
  375.                     {
  376.                         return;
  377.                     }
  378.                     //两个形状都设置了,便添加一条新连线
  379.                     Links.Add(new LineLink()
  380.                     {
  381.                         Id = "连线" + (Links.Count + 1),
  382.                         StartShapeId =_selectedStartIsRect? _selectedStartRectShape.Id:_selectedStartEllipseShape.Id,
  383.                         EndShapeId =_selectedEndIsRect? _selectedEndRectShape.Id:_selectedEndEllipseShape.Id,
  384.                         StartShapeIsRect=_selectedStartIsRect,
  385.                         EndShapeIsRect=_selectedEndIsRect,
  386.                     });
  387.                     //两个形状都已选择,结束添加连线状态
  388.                     _isAddLink = false;
  389.                     toolStripStatusLabel1.Text = "";
  390.                     //重绘以显示连线
  391.                     DrawAll(panel1.CreateGraphics());
  392.                 }
  393.             }
  394.         }
  395.         private void panel1_MouseMove(object sender, MouseEventArgs e)
  396.         {
  397.             //当鼠标移动时
  398.             //如果处于添加连线时,则不移动形状
  399.             if (_isAddLink) return;
  400.             if (_isMouseDown)
  401.             {
  402.                 //当且仅当:有鼠标按下且有矩形被选中时,才进行后续操作
  403.                 //改变选中矩形的位置信息,随着鼠标移动而移动
  404.                 //计算鼠标位置变化信息
  405.                 var moveX = e.Location.X - _lastMouseLocation.X;
  406.                 var moveY = e.Location.Y - _lastMouseLocation.Y;
  407.                 //将选中形状的位置进行同样的变化
  408.                 if (_isRectMouseDown)
  409.                 {
  410.                     var oldXY = _selectedRectShape.Rect.Location;
  411.                     oldXY.Offset(moveX, moveY);
  412.                     _selectedRectShape.Rect = new Rectangle(oldXY, _selectedRectShape.Rect.Size);
  413.                 }
  414.                 else
  415.                 {
  416.                     var oldXY = _selectedEllipseShape.Rect.Location;
  417.                     oldXY.Offset(moveX, moveY);
  418.                     _selectedEllipseShape.Rect = new Rectangle(oldXY, _selectedEllipseShape.Rect.Size);
  419.                 }
  420.                 //记录当前鼠标位置
  421.                 _lastMouseLocation.Offset(moveX, moveY);
  422.                 //重绘所有矩形
  423.                 DrawAll(panel1.CreateGraphics());
  424.             }
  425.         }
  426.         private void panel1_MouseUp(object sender, MouseEventArgs e)
  427.         {
  428.             //当鼠标松开时
  429.             if (_isMouseDown)
  430.             {
  431.                 //当且仅当:有鼠标按下且有矩形被选中时,才进行后续操作
  432.                 //重置相关记录信息
  433.                 _isMouseDown = false;
  434.                 _lastMouseLocation = Point.Empty;
  435.                 _selectedRectShape = null;
  436.                 _selectedEllipseShape = null;
  437.             }
  438.         }
  439.         private void toolStripButton2_Click(object sender, EventArgs e)
  440.         {
  441.             _isAddLink = true;
  442.             _selectedStartRectShape = null;
  443.             _selectedEndRectShape = null;
  444.             _selectedStartEllipseShape = null;
  445.             _selectedEndEllipseShape = null;
  446.             toolStripStatusLabel1.Text = "请点击第1个形状";
  447.         }
  448.         private void toolStripButton3_Click(object sender, EventArgs e)
  449.         {
  450.             _isAddLink = false;
  451.             _selectedStartRectShape = null;
  452.             _selectedEndRectShape = null;
  453.             toolStripStatusLabel1.Text = "";
  454.             DrawAll(panel1.CreateGraphics());
  455.         }
  456.         private void toolStripButton4_Click(object sender, EventArgs e)
  457.         {
  458.             var rs = new EllipseShape()
  459.             {
  460.                 Id = "圆形" + (EllipseShapes.Count + 1),
  461.                 Rect = new Rectangle()
  462.                 {
  463.                     X = 50,
  464.                     Y = 50,
  465.                     Width = 100,
  466.                     Height = 100,
  467.                 },
  468.             };
  469.             EllipseShapes.Add(rs);
  470.             //重绘所有矩形
  471.             DrawAll(panel1.CreateGraphics());
  472.         }
  473.     }
  474. }
复制代码
四、实现效果2:解决拖动慢、闪烁等题目

本来未设置显示质量时,拖动外形时就会闪烁,而经过上面的设置后,拖动变慢了,闪烁更严峻了。
闪烁是因为我们每次拖动都是整个控件清空再依次绘制全部外形和连线,这些耗时就会导致闪烁题目。而在设置为高质量显示后,绘制更慢,闪烁便会更明显,拖动起来也感觉不跟手变慢了。
我们下面就来解决一下这个题目。
注:更详细深入的图文解说在这个教程里有说明,不再赘述:https://www.cnblogs.com/lesliexin/p/16554752.html
1,开启双缓冲

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

当然,仅仅开启双缓冲效果并不大,还要搭配下面的处理才行。
2,使用内存绘图

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

如上所示,我们先创建一个与控件尺寸一样大的位图对象,然后在此位图上绘制全部连线,末了绘制到控件上。
好了,到此我们就实现了效果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 FormDemo04V1 : FormBase
  13.     {
  14.         public FormDemo04V1()
  15.         {
  16.             InitializeComponent();
  17.             DemoTitle = "第06节随课Demo  Part1";
  18.             DemoNote = "效果:优化拖动慢、拖动时闪烁等问题";
  19.             SetStyle(ControlStyles.AllPaintingInWmPaint |
  20.                 ControlStyles.UserPaint |
  21.                 ControlStyles.OptimizedDoubleBuffer,true);
  22.         }
  23.         /// <summary>
  24.         /// 矩形定义
  25.         /// </summary>
  26.         public class RectShape
  27.         {
  28.             /// <summary>
  29.             /// 矩形ID
  30.             /// </summary>
  31.             public string Id { get; set; }
  32.             /// <summary>
  33.             /// 矩形位置和尺寸
  34.             /// </summary>
  35.             public Rectangle Rect { get; set; }
  36.         }
  37.         /// <summary>
  38.         /// 圆形定义
  39.         /// </summary>
  40.         public class EllipseShape
  41.         {
  42.             /// <summary>
  43.             /// 矩形ID
  44.             /// </summary>
  45.             public string Id { get; set; }
  46.             /// <summary>
  47.             /// 矩形位置和尺寸
  48.             /// </summary>
  49.             public Rectangle Rect { get; set; }
  50.         }
  51.         /// <summary>
  52.         /// 直线连线定义
  53.         /// </summary>
  54.         public class LineLink
  55.         {
  56.             /// <summary>
  57.             /// 连线ID
  58.             /// </summary>
  59.             public string Id { get; set; }
  60.             /// <summary>
  61.             /// 开始形状是否是矩形
  62.             /// </summary>
  63.             public bool StartShapeIsRect { get; set; }
  64.             /// <summary>
  65.             /// 结束开关是否是矩形
  66.             /// </summary>
  67.             public bool EndShapeIsRect { get; set; }
  68.             /// <summary>
  69.             /// 连线开始形状ID
  70.             /// </summary>
  71.             public string StartShapeId { get; set; }
  72.             /// <summary>
  73.             /// 连线结束形状ID
  74.             /// </summary>
  75.             public string EndShapeId { get; set; }
  76.         }
  77.         /// <summary>
  78.         /// 矩形集合
  79.         /// </summary>
  80.         List<RectShape> RectShapes = new List<RectShape>();
  81.         /// <summary>
  82.         /// 圆形集合
  83.         /// </summary>
  84.         List<EllipseShape> EllipseShapes = new List<EllipseShape>();
  85.         /// <summary>
  86.         /// 当前界面连线集合
  87.         /// </summary>
  88.         List<LineLink> Links = new List<LineLink>();
  89.         /// <summary>
  90.         /// 画一个矩形(不同颜色)
  91.         /// </summary>
  92.         /// <param name="g"></param>
  93.         /// <param name="shape"></param>
  94.         void DrawRectShape2(Graphics g, RectShape shape)
  95.         {
  96.             var index = RectShapes.FindIndex(a => a.Id == shape.Id);
  97.             g.FillRectangle(GetBrush(index), shape.Rect);
  98.             g.DrawString(shape.Id, Font, Brushes.White, shape.Rect);
  99.         }
  100.         /// <summary>
  101.         /// 画一个圆形(不同颜色)
  102.         /// </summary>
  103.         /// <param name="g"></param>
  104.         /// <param name="shape"></param>
  105.         void DrawEllipseShape2(Graphics g, EllipseShape shape)
  106.         {
  107.             var index = EllipseShapes.FindIndex(a => a.Id == shape.Id);
  108.             g.FillEllipse(GetBrush(index), shape.Rect);
  109.             g.DrawString(shape.Id, Font, Brushes.White, shape.Rect.X+20,shape.Rect.Y+20);            //注:这里可以讲一下,要+20,是显示文本
  110.         }
  111.         /// <summary>
  112.         /// 绘制一条连线(不同颜色)
  113.         /// </summary>
  114.         /// <param name="g"></param>
  115.         /// <param name="line"></param>
  116.         void DrawLine2(Graphics g, LineLink line)
  117.         {
  118.             //通过连线的开始形状ID和结束形状ID,计算两个形状的中心点坐标
  119.             var startPoint = line.StartShapeIsRect? GetCentertPointRect(line.StartShapeId): GetCentertPointEllipse(line.StartShapeId);
  120.             var endPoint =line.EndShapeIsRect? GetCentertPointRect(line.EndShapeId) : GetCentertPointEllipse(line.EndShapeId);
  121.             var index = Links.FindIndex(a => a.Id == line.Id);
  122.             //绘制一条直线
  123.             g.DrawLine(GetPen(index), startPoint, endPoint);
  124.         }
  125.         //注:文章中说明,参见下面的地址,讲的很清楚。
  126.         Bitmap _bmp;
  127.         /// <summary>
  128.         /// 重新绘制当前所有矩形和连线
  129.         /// </summary>
  130.         /// <param name="g"></param>
  131.         void DrawAll(Graphics g1)
  132.         {
  133.             //创建内存绘图,将形状和连线绘制到此内存绘图上,然后再一次性绘制到控件上
  134.             _bmp = new Bitmap(panel1.Width, panel1.Height);
  135.             var g = Graphics.FromImage(_bmp);
  136.             //设置显示质量
  137.             g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
  138.             g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
  139.             g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
  140.             g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
  141.             g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
  142.             g.Clear(panel1.BackColor);
  143.             //绘制所有矩形
  144.             foreach (var sp in RectShapes)
  145.             {
  146.                 DrawRectShape2(g, sp);
  147.             }
  148.             //绘制所有圆形
  149.             foreach (var sp in EllipseShapes)
  150.             {
  151.                 DrawEllipseShape2(g, sp);
  152.             }
  153.             //绘制所有连线
  154.             foreach (var ln in Links)
  155.             {
  156.                 DrawLine2(g, ln);
  157.             }
  158.             //绘制内存绘图到控件上
  159.             g1.DrawImage(_bmp, new PointF(0, 0));
  160.         }
  161.         //注:文章中说明;此处不过于抽象,后续章节会有
  162.         /// <summary>
  163.         /// 当前是否有鼠标按下,且有矩形被选中
  164.         /// </summary>
  165.         bool _isMouseDown = false;
  166.         /// <summary>
  167.         /// 是否是矩形被选中,不是则是圆形
  168.         /// </summary>
  169.         bool _isRectMouseDown = true;
  170.         /// <summary>
  171.         /// 最后一次鼠标的位置
  172.         /// </summary>
  173.         Point _lastMouseLocation = Point.Empty;
  174.         /// <summary>
  175.         /// 当前被鼠标选中的矩形
  176.         /// </summary>
  177.         RectShape _selectedRectShape = null;
  178.         /// <summary>
  179.         /// 当前被鼠标选中的圆形
  180.         /// </summary>
  181.         EllipseShape _selectedEllipseShape = null;
  182.         /// <summary>
  183.         /// 添加连线时选中的第一个是否是矩形,不是则是圆形
  184.         /// </summary>
  185.         bool _selectedStartIsRect = true;
  186.         /// <summary>
  187.         /// 添加连线时选中的第一个矩形
  188.         /// </summary>
  189.         RectShape _selectedStartRectShape = null;
  190.         /// <summary>
  191.         /// 添加连线时选中的第一个圆形
  192.         /// </summary>
  193.         EllipseShape _selectedStartEllipseShape = null;
  194.         /// <summary>
  195.         /// 添加连线时选中的第二个是否是矩形,不是则是圆形
  196.         /// </summary>
  197.         bool _selectedEndIsRect = true;
  198.         /// <summary>
  199.         /// 添加连线时选中的第二个矩形
  200.         /// </summary>
  201.         RectShape _selectedEndRectShape = null;
  202.         /// <summary>
  203.         /// 添加连线时选中的第二个圆形
  204.         /// </summary>
  205.         EllipseShape _selectedEndEllipseShape = null;
  206.         /// <summary>
  207.         /// 是否正添加连线
  208.         /// </summary>
  209.         bool _isAddLink = false;
  210.         /// <summary>
  211.         /// 获取不同的背景颜色
  212.         /// </summary>
  213.         /// <param name="i"></param>
  214.         /// <returns></returns>
  215.         Brush GetBrush(int i)
  216.         {
  217.             switch (i)
  218.             {
  219.                 case 0: return Brushes.Red;
  220.                 case 1: return Brushes.Green;
  221.                 case 2: return Brushes.Blue;
  222.                 case 3: return Brushes.Orange;
  223.                 case 4: return Brushes.Purple;
  224.                 default: return Brushes.Red;
  225.             }
  226.         }
  227.         /// <summary>
  228.         /// 获取不同的画笔颜色
  229.         /// </summary>
  230.         /// <param name="i"></param>
  231.         /// <returns></returns>
  232.         Pen GetPen(int i)
  233.         {
  234.             return new Pen(GetBrush(i), 2);
  235.         }
  236.         /// <summary>
  237.         /// 根据形状ID获取形状的中心点,以作为连线的起点或终点
  238.         /// </summary>
  239.         /// <param name="shapeId"></param>
  240.         /// <returns></returns>
  241.         Point GetCentertPointRect(string shapeId)
  242.         {
  243.             var sp = RectShapes.Find(a => a.Id == shapeId);
  244.             if (sp != null)
  245.             {
  246.                 var line1X = sp.Rect.X + sp.Rect.Width / 2;
  247.                 var line1Y = sp.Rect.Y + sp.Rect.Height / 2;
  248.                 return new Point(line1X, line1Y);
  249.             }
  250.             return Point.Empty;
  251.         }
  252.         /// <summary>
  253.         /// 根据形状ID获取形状的中心点,以作为连线的起点或终点
  254.         /// </summary>
  255.         /// <param name="shapeId"></param>
  256.         /// <returns></returns>
  257.         Point GetCentertPointEllipse(string shapeId)
  258.         {
  259.             var sp = EllipseShapes.Find(a => a.Id == shapeId);
  260.             if (sp != null)
  261.             {
  262.                 var line1X = sp.Rect.X + sp.Rect.Width / 2;
  263.                 var line1Y = sp.Rect.Y + sp.Rect.Height / 2;
  264.                 return new Point(line1X, line1Y);
  265.             }
  266.             return Point.Empty;
  267.         }
  268.         private void toolStripButton1_Click(object sender, EventArgs e)
  269.         {
  270.             var rs = new RectShape()
  271.             {
  272.                 Id = "矩形" + (RectShapes.Count + 1),
  273.                 Rect = new Rectangle()
  274.                 {
  275.                     X = 50,
  276.                     Y = 50,
  277.                     Width = 100,
  278.                     Height = 100,
  279.                 },
  280.             };
  281.             RectShapes.Add(rs);
  282.             //重绘所有矩形
  283.             DrawAll(panel1.CreateGraphics());
  284.         }
  285.         private void panel1_MouseDown(object sender, MouseEventArgs e)
  286.         {
  287.             //当鼠标按下时
  288.             //取最上方的矩形,也就是最后添加的矩形
  289.             var sp = RectShapes.FindLast(a => a.Rect.Contains(e.Location));
  290.             //取最上方的圆形,也就是最后添加的圆形
  291.             var ep = EllipseShapes.FindLast(a => a.Rect.Contains(e.Location));
  292.             if (!_isAddLink)
  293.             {
  294.                 //注:说明,这里是简化情况,因为是两个LIST,无法判断序号,就先判断矩形
  295.                
  296.                 //当前没有处理连线状态
  297.                 if (sp != null)
  298.                 {
  299.                     //设置状态及选中矩形
  300.                     _isMouseDown = true;
  301.                     _lastMouseLocation = e.Location;
  302.                     _selectedRectShape = sp;
  303.                     _selectedEllipseShape = null;
  304.                     _isRectMouseDown = true;
  305.                 }
  306.                 else if (ep != null)
  307.                 {
  308.                     //设置状态及选中圆形
  309.                     _isMouseDown = true;
  310.                     _lastMouseLocation = e.Location;
  311.                     _selectedRectShape = null;
  312.                     _selectedEllipseShape = ep;
  313.                     _isRectMouseDown = false;
  314.                 }
  315.             }
  316.             else
  317.             {
  318.                 //正在添加连线
  319.                 if (_selectedStartRectShape == null && _selectedStartEllipseShape == null)
  320.                 {
  321.                     //证明没有矩形和圆形被选中则设置开始形状
  322.                     if (sp != null)
  323.                     {
  324.                         //设置开始形状是矩形
  325.                         _selectedStartRectShape = sp;
  326.                         _selectedStartEllipseShape = null;
  327.                         _selectedStartIsRect = true;
  328.                     }
  329.                     else if (ep != null)
  330.                     {
  331.                         //设置开始形状是圆形
  332.                         _selectedStartRectShape = null;
  333.                         _selectedStartEllipseShape = ep;
  334.                         _selectedStartIsRect = false;
  335.                     }
  336.                     toolStripStatusLabel1.Text = "请点击第2个形状";
  337.                 }
  338.                 else
  339.                 {
  340.                     //判断第2个形状是否是第1个形状
  341.                     if (sp != null)
  342.                     {
  343.                         //证明当前选中的是矩形
  344.                         if (_selectedStartRectShape != null)
  345.                         {
  346.                             //证明第1步选中的矩形
  347.                             //判断当前选中的矩形是否是第1步选中的矩形
  348.                             if (_selectedStartRectShape.Id == sp.Id)
  349.                             {
  350.                                 toolStripStatusLabel1.Text = "不可选择同一个形状,请重新点击第2个形状";
  351.                                 return;
  352.                             }
  353.                         }
  354.                     }
  355.                     else if (ep != null)
  356.                     {
  357.                         //证明当前选中的圆形
  358.                         if (_selectedStartEllipseShape != null)
  359.                         {
  360.                             //证明第1步选中的矩形
  361.                             //判断当前选中的矩形是否是第1步选中的矩形
  362.                             if (_selectedStartEllipseShape.Id == ep.Id)
  363.                             {
  364.                                 toolStripStatusLabel1.Text = "不可选择同一个形状,请重新点击第2个形状";
  365.                                 return;
  366.                             }
  367.                         }
  368.                     }
  369.                     //注:文章中说明:因为太过复杂,且不是本节重点,但不再进行去重判断
  370.                     if (sp != null)
  371.                     {
  372.                         //设置结束形状是矩形
  373.                         _selectedEndRectShape = sp;
  374.                         _selectedEndEllipseShape = null;
  375.                         _selectedEndIsRect = true;
  376.                     }
  377.                     else if (ep != null)
  378.                     {
  379.                         //设置结束形状是圆形
  380.                         _selectedEndRectShape = null;
  381.                         _selectedEndEllipseShape = ep;
  382.                         _selectedEndIsRect = false;
  383.                     }
  384.                     else
  385.                     {
  386.                         return;
  387.                     }
  388.                     //两个形状都设置了,便添加一条新连线
  389.                     Links.Add(new LineLink()
  390.                     {
  391.                         Id = "连线" + (Links.Count + 1),
  392.                         StartShapeId =_selectedStartIsRect? _selectedStartRectShape.Id:_selectedStartEllipseShape.Id,
  393.                         EndShapeId =_selectedEndIsRect? _selectedEndRectShape.Id:_selectedEndEllipseShape.Id,
  394.                         StartShapeIsRect=_selectedStartIsRect,
  395.                         EndShapeIsRect=_selectedEndIsRect,
  396.                     });
  397.                     //两个形状都已选择,结束添加连线状态
  398.                     _isAddLink = false;
  399.                     toolStripStatusLabel1.Text = "";
  400.                     //重绘以显示连线
  401.                     DrawAll(panel1.CreateGraphics());
  402.                 }
  403.             }
  404.         }
  405.         private void panel1_MouseMove(object sender, MouseEventArgs e)
  406.         {
  407.             //当鼠标移动时
  408.             //如果处于添加连线时,则不移动形状
  409.             if (_isAddLink) return;
  410.             if (_isMouseDown)
  411.             {
  412.                 //当且仅当:有鼠标按下且有矩形被选中时,才进行后续操作
  413.                 //改变选中矩形的位置信息,随着鼠标移动而移动
  414.                 //计算鼠标位置变化信息
  415.                 var moveX = e.Location.X - _lastMouseLocation.X;
  416.                 var moveY = e.Location.Y - _lastMouseLocation.Y;
  417.                 //将选中形状的位置进行同样的变化
  418.                 if (_isRectMouseDown)
  419.                 {
  420.                     var oldXY = _selectedRectShape.Rect.Location;
  421.                     oldXY.Offset(moveX, moveY);
  422.                     _selectedRectShape.Rect = new Rectangle(oldXY, _selectedRectShape.Rect.Size);
  423.                 }
  424.                 else
  425.                 {
  426.                     var oldXY = _selectedEllipseShape.Rect.Location;
  427.                     oldXY.Offset(moveX, moveY);
  428.                     _selectedEllipseShape.Rect = new Rectangle(oldXY, _selectedEllipseShape.Rect.Size);
  429.                 }
  430.                 //记录当前鼠标位置
  431.                 _lastMouseLocation.Offset(moveX, moveY);
  432.                 //重绘所有矩形
  433.                 DrawAll(panel1.CreateGraphics());
  434.             }
  435.         }
  436.         private void panel1_MouseUp(object sender, MouseEventArgs e)
  437.         {
  438.             //当鼠标松开时
  439.             if (_isMouseDown)
  440.             {
  441.                 //当且仅当:有鼠标按下且有矩形被选中时,才进行后续操作
  442.                 //重置相关记录信息
  443.                 _isMouseDown = false;
  444.                 _lastMouseLocation = Point.Empty;
  445.                 _selectedRectShape = null;
  446.                 _selectedEllipseShape = null;
  447.             }
  448.         }
  449.         private void toolStripButton2_Click(object sender, EventArgs e)
  450.         {
  451.             _isAddLink = true;
  452.             _selectedStartRectShape = null;
  453.             _selectedEndRectShape = null;
  454.             _selectedStartEllipseShape = null;
  455.             _selectedEndEllipseShape = null;
  456.             toolStripStatusLabel1.Text = "请点击第1个形状";
  457.         }
  458.         private void toolStripButton3_Click(object sender, EventArgs e)
  459.         {
  460.             _isAddLink = false;
  461.             _selectedStartRectShape = null;
  462.             _selectedEndRectShape = null;
  463.             toolStripStatusLabel1.Text = "";
  464.             DrawAll(panel1.CreateGraphics());
  465.         }
  466.         private void toolStripButton4_Click(object sender, EventArgs e)
  467.         {
  468.             var rs = new EllipseShape()
  469.             {
  470.                 Id = "圆形" + (EllipseShapes.Count + 1),
  471.                 Rect = new Rectangle()
  472.                 {
  473.                     X = 50,
  474.                     Y = 50,
  475.                     Width = 100,
  476.                     Height = 100,
  477.                 },
  478.             };
  479.             EllipseShapes.Add(rs);
  480.             //重绘所有矩形
  481.             DrawAll(panel1.CreateGraphics());
  482.         }
  483.     }
  484. }
复制代码
五、结语

本节课程很简单,是基于当前为止的必要,简单的解决了显示质量和拖动闪烁的题目,当然上面的代码并不非常完善,像资源释放、全屏显示时仍大概出现拖动不跟手等题目都没作处理,这些不是本节重点,我们会在后面的课程里一一解决。
下节课程我们就来对矩形、圆形等外形和连线做一个抽象,以解决前面课程遇到的增长一个新的外形时代码就要添加好多额外判断代码的题目,以及为后续的支持菱形、平行四边形等任不测形做底子。当然连线也是,后面不止有直线,另有贝塞尔曲线、正交连线等各种连线。
感谢各人的观看,本人水平有限,文章不足之处接待各人评论指正。
-[END]-

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表