每分每秒 發表於 2025-7-30 13:28:00

[原创]《C#高级GDI+实战:从零开发一个流程图》第08章:增加菱形、平行四边形、圆角矩形,文本居中显示

<h1 id="一前言">一、前言</h1>
<p>前面的课程我们已经完成了形状和连线的抽象,并独立出了画布控件,基础已经打好,下面就要添砖加瓦了。我们本节课程就来添加一些不同的形状,如:菱形、平行四边形、圆角矩形等。而且我们前面发现形状内的文本都不是居中显示的,我们也顺便优化下。</p>
<p><strong>相信看完的你,一定会有所收获!</strong></p>
<p>本文地址:https://www.cnblogs.com/lesliexin/p/18997090</p>
<h1 id="二先看效果">二、先看效果</h1>
<p>我们可以看到添加了不同的形状,且都支持拖动、连线。</p>
<p><iframe src="https://player.bilibili.com/player.html?isOutside=true&amp;bvid=BV1vjTRz1Ezo&amp;p=1&amp;autoplay=0" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true" width="668" height="376"></iframe></p>
<p>看完本篇的代码,我们会发现实现起来很简单,只需要给继承形状基类实现下就行了,主程序只是添加个控件、生成个形状类,调用画布的添加形状方法就行了。</p>
<p>所以本节的课程重点在于如何去用GDI+“画”出这些形状。</p>
<h1 id="三菱形">三、菱形</h1>
<p>像菱形及本文中的形状,就没有现在的GDI+方法来实现了,只能通过各个子方法来组合绘制出想要的形状。</p>
<p>我们先看下图的菱形图示:</p>
<p><img src="https://img2024.cnblogs.com/blog/1686429/202507/1686429-20250725150838325-1489795973.png" alt="image" loading="lazy"></p>
<p>我们的所做的就是依次绘制菱形的四个边,这四个顶点的坐标怎么来的呢?</p>
<p>我们在前面的抽象出形状基类那节讲过,<strong>属性Rect是指示形状所在的矩形区域,所以我们就要在这个矩形区域内,指定4个顶点并计算出坐标。</strong></p>
<p>当有了坐标后,我可以使用GDI+的AddPolygon方法来将多个坐标点添加成一个多边形,其MSDN的解释如下:</p>
<p><img src="https://img2024.cnblogs.com/blog/1686429/202507/1686429-20250725151119647-1128108285.png" alt="image" loading="lazy"></p>
<p>最后使用GDI+的FillPath将此多边形绘制出来,具体的代码如下:</p>
<p><img src="https://img2024.cnblogs.com/blog/1686429/202507/1686429-20250725151335699-1514662969.png" alt="image" loading="lazy"></p>
<h1 id="四平行四边形">四、平行四边形</h1>
<p>同菱形,我们也是使用类似的方法求出四个顶点的坐标,这里我们将倾斜距离设置为1/5的宽度:</p>
<p><img src="https://img2024.cnblogs.com/blog/1686429/202507/1686429-20250725151535850-1833248906.png" alt="image" loading="lazy"></p>
<p>代码定义里直接按图示取值即可:</p>
<p><img src="https://img2024.cnblogs.com/blog/1686429/202507/1686429-20250725151529300-741050108.png" alt="image" loading="lazy"></p>
<h1 id="五圆角矩形">五、圆角矩形</h1>
<p>圆角矩形就和上面的两个形状不一样了,因为不再是由直线组成,而是要有弧度:</p>
<p><img src="https://img2024.cnblogs.com/blog/1686429/202507/1686429-20250725152134463-1718249165.png" alt="image" loading="lazy"></p>
<p>这里要使用一个新的GDI+方法:AddArc,添加一段弧线,其MSDN的解释如下:</p>
<p><img src="https://img2024.cnblogs.com/blog/1686429/202507/1686429-20250725153138324-1066426187.png" alt="image" loading="lazy"></p>
<p>注意看最下面那段话:</p>
<blockquote>
<p>如果图中有上一条直线或曲线,则会添加一条线,用于将上一段的端点连接到弧线的开头。</p>
</blockquote>
<p>所以我们并不需要添加4条直线4个弧线,只需要添加4个弧线就行了,我们暂时将弧线所在圆的直径固定为20。</p>
<p>其中:</p>
<h4 id="1左上角">1,左上角</h4>
<p><img src="https://img2024.cnblogs.com/blog/1686429/202507/1686429-20250725153315428-944398974.png" alt="image" loading="lazy"></p>
<h4 id="2右上角">2,右上角</h4>
<p><img src="https://img2024.cnblogs.com/blog/1686429/202507/1686429-20250725153327339-806713977.png" alt="image" loading="lazy"></p>
<h4 id="3右下角">3,右下角</h4>
<p><img src="https://img2024.cnblogs.com/blog/1686429/202507/1686429-20250725153336574-1000270226.png" alt="image" loading="lazy"></p>
<h4 id="4左下角">4,左下角</h4>
<p><img src="https://img2024.cnblogs.com/blog/1686429/202507/1686429-20250725153350005-1673638121.png" alt="image" loading="lazy"></p>
<p>我们参照上图的坐标及角度编写代码即可:</p>
<p><img src="https://img2024.cnblogs.com/blog/1686429/202507/1686429-20250725153633525-299773760.png" alt="image" loading="lazy"></p>
<h1 id="六文本居中显示">六、文本居中显示</h1>
<p>上面的形状实现后,我们会发现文本位置都不统一,我们下面就来让文本统一居中显示。</p>
<p>核心是使用GDI+的DrawString的一个重载方法:</p>
<p><img src="https://img2024.cnblogs.com/blog/1686429/202507/1686429-20250725154731310-1825051192.png" alt="image" loading="lazy"></p>
<p>我们像下面这样写就能让文本居中显示:</p>
<p><img src="https://img2024.cnblogs.com/blog/1686429/202507/1686429-20250725155616758-299588062.png" alt="image" loading="lazy"></p>
<p>关于StringFormat的详细讲解,请参照教程:</p>
<p>C# StringFormat详解之文本方向、对齐</p>
<p>https://www.cnblogs.com/lesliexin/p/12879270.html</p>
<p>具体的代码改造如下,不再赘述:</p>
<details>
<summary>点击查看代码</summary>
<pre><code>
    /// &lt;summary&gt;
    /// 菱形定义
    /// &lt;/summary&gt;
    public class LozengeShapeV2 : ShapeBase
    {
      public override void Draw(Graphics g)
      {
            var x2 = Rect.X;
            var y2 = Rect.Y;
            var w2 = Rect.Width;
            var h2 = Rect.Height;

            var x = x2 + w2 / 2;
            var y = y2 + h2 / 2;

            //左-上-右-下
            var r0 = new Point(x2, y);
            var r1 = new Point(x, y2);
            var r2 = new Point(x2 + w2, y);
            var r3 = new Point(x, y2 + h2);

            var path = new GraphicsPath();
            path.Reset();
            path.AddPolygon(new Point[]{ r0,r1,r2,r3});
            path.CloseFigure();
            g.FillPath(new SolidBrush(BackgroundColor), path);

            g.DrawString(Text, TextFont, new SolidBrush(FontColor), Rect,
                new StringFormat() { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center });
      }

    }
       
       
    /// &lt;summary&gt;
    /// 平行四边形定义
    /// &lt;/summary&gt;
    public class ParallelogramShapeV2 : ShapeBase
    {
      public override void Draw(Graphics g)
      {
            var x2 = Rect.X;
            var y2 = Rect.Y;
            var w2 = Rect.Width;
            var h2 = Rect.Height;

            var f = w2 / 5;

            //左-上-右-下
            var r0 = new Point(x2 + f, y2);
            var r1 = new Point(x2 + w2, y2);
            var r2 = new Point(x2 + w2 - f, y2 + h2);
            var r3 = new Point(x2, y2 + h2);

            var path = new GraphicsPath();
            path.Reset();
            path.AddPolygon(new Point[]{ r0,r1,r2,r3});
            path.CloseFigure();
            g.FillPath(new SolidBrush(BackgroundColor), path);

            g.DrawString(Text, TextFont, new SolidBrush(FontColor), Rect,
                new StringFormat() { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center });
      }

    }
       
       
    /// &lt;summary&gt;
    /// 圆角矩形定义
    /// &lt;/summary&gt;
    public class RoundRectShapeV2 : ShapeBase
    {
      public override void Draw(Graphics g)
      {
            float diameter = 20;

            var path = new GraphicsPath();
            path.Reset();

            RectangleF arc = new RectangleF(Rect.X, Rect.Y, diameter, diameter);
         
            // 左上角
            path.AddArc(arc, 180, 90);

            // 右上角
            arc.X = Rect.Right - diameter;
            path.AddArc(arc, 270, 90);

            // 右下角
            arc.Y = Rect.Bottom - diameter;
            path.AddArc(arc, 0, 90);

            // 左下角
            arc.X = Rect.Left;
            path.AddArc(arc, 90, 90);


            path.CloseFigure();
            g.FillPath(new SolidBrush(BackgroundColor), path);

            g.DrawString(Text, TextFont, new SolidBrush(FontColor), Rect,
                new StringFormat() { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center });
      }

    }
</code></pre>
</details>
<h1 id="七添加形状方法抽象出泛型方法">七、添加形状方法抽象出泛型方法</h1>
<p>我们看之前添加矩形和圆形的方法:</p>
<p><img src="https://img2024.cnblogs.com/blog/1686429/202507/1686429-20250725161332920-104957669.png" alt="image" loading="lazy"></p>
<p>会发现很类似,类似是因为矩形和圆形都是形状基类的实现,那么我们本节课程添加了这三个新的形状,再这样写就太繁琐了,我们直接抽象出一个泛型方法来解决此问题:</p>
<p><img src="https://img2024.cnblogs.com/blog/1686429/202507/1686429-20250725161531439-1572576272.png" alt="image" loading="lazy"></p>
<p>可以看到,就是将生成矩形和圆形的方法使用泛型替代。</p>
<p>我们再写一个泛型方法来将形状添加到画布:</p>
<p><img src="https://img2024.cnblogs.com/blog/1686429/202507/1686429-20250725161644173-1348957761.png" alt="image" loading="lazy"></p>
<p>好了,到此我们的代码就进一步简化了,添加不同形状只需要传入对应的形状类型就行了:</p>
<p><img src="https://img2024.cnblogs.com/blog/1686429/202507/1686429-20250725161741933-25678363.png" alt="image" loading="lazy"></p>
<p>是不是很优雅~</p>
<p>完整代码如下,大家可自行尝试:</p>
<details>
<summary>点击查看代码</summary>
<pre><code>using Elements;
using Elements.Links;
using Elements.Shapes;
using FlowChartCanvas;
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 FormDemo07V4 : FormBase
    {
      public FormDemo07V4()
      {
            InitializeComponent();
            DemoTitle = "第09节随课DemoPart4";
            DemoNote = "效果:所有形状内文本居中显示。";

            //添加画布控件
            _fcc = new FCCanvasV1();
            _fcc.FCC_LinkColor += _fcc_FCC_LinkColor;
            _fcc.FCC_LinkState += _fcc_FCC_LinkState;
            _fcc.Dock = DockStyle.Fill;
            panel1.Controls.Add(_fcc);

      }

      private void _fcc_FCC_LinkState(string obj)
      {
            toolStripStatusLabel1.Text = obj;
      }

      private Color _fcc_FCC_LinkColor()
      {
            return GetColor(_linkColorIndex++);
      }

      FCCanvasV1 _fcc;

      /// &lt;summary&gt;
      /// 形状颜色序号
      /// &lt;/summary&gt;
      int _shapeColorIndex = 0;
      /// &lt;summary&gt;
      /// 连线颜色序号
      /// &lt;/summary&gt;
      int _linkColorIndex = 0;
               
      /// &lt;summary&gt;
      /// 获取不同的背景颜色
      /// &lt;/summary&gt;
      /// &lt;param name="i"&gt;&lt;/param&gt;
      /// &lt;returns&gt;&lt;/returns&gt;
      Color GetColor(int i)
      {
            switch (i)
            {
                case 0: return Color.Red;
                case 1: return Color.Green;
                case 2: return Color.Blue;
                case 3: return Color.Orange;
                case 4: return Color.Purple;
                default: return Color.Red;
            }
      }

      //注:文章中说明:再次抽象

      /// &lt;summary&gt;
      /// 创建形状
      /// &lt;/summary&gt;
      /// &lt;typeparam name="T"&gt;&lt;/typeparam&gt;
      /// &lt;param name="shapeText"&gt;&lt;/param&gt;
      /// &lt;returns&gt;&lt;/returns&gt;
      T CreateShape&lt;T&gt;(string shapeText) where T:ShapeBase
      {
            var t = Activator.CreateInstance&lt;T&gt;();
            t.Id = shapeText + Guid.NewGuid().ToString();
            t.Rect = new Rectangle()
            {
                X = 50,
                Y = 50,
                Width = 100,
                Height = 100,
            };
            t.FontColor = Color.White;
            t.BackgroundColor = GetColor(_shapeColorIndex++);
            t.Text = shapeText + _shapeColorIndex;
            t.TextFont = Font;
            return t;
      }

      /// &lt;summary&gt;
      /// 创建指定类型的形状并添加到当前流程图画布中。
      /// &lt;/summary&gt;
      /// &lt;typeparam name="T"&gt;&lt;/typeparam&gt;
      /// &lt;param name="shapeText"&gt;&lt;/param&gt;
      void CreateShapeAndAddToFCCanvas&lt;T&gt;(string shapeText) where T : ShapeBase
      {
            var sp = CreateShape&lt;T&gt;(shapeText);

            _fcc.FCC_AddShapes(new List&lt;ShapeBase&gt;() { sp });
            _fcc.FCC_Refresh();
      }

      private void toolStripButton1_Click(object sender, EventArgs e)
      {
            CreateShapeAndAddToFCCanvas&lt;RectShapeV2&gt;("矩形");
      }

      private void toolStripButton4_Click(object sender, EventArgs e)
      {
            CreateShapeAndAddToFCCanvas&lt;EllipseShapeV2&gt;("圆形");
      }

      private void toolStripButton5_Click(object sender, EventArgs e)
      {
            CreateShapeAndAddToFCCanvas&lt;LozengeShapeV2&gt;("菱形");
      }

      private void toolStripButton6_Click(object sender, EventArgs e)
      {
            CreateShapeAndAddToFCCanvas&lt;ParallelogramShapeV2&gt;("平行四边形");
      }

      private void toolStripButton7_Click(object sender, EventArgs e)
      {
            CreateShapeAndAddToFCCanvas&lt;RoundRectShapeV2&gt;("圆角矩形");
      }

      private void toolStripButton2_Click(object sender, EventArgs e)
      {
            _fcc.FCC_StartLink();
      }

      private void toolStripButton3_Click(object sender, EventArgs e)
      {
            _fcc.FCC_StopLink();
      }

    }


}

</code></pre>
</details>
<h1 id="八结语">八、结语</h1>
<p>我们本节课添加了多个不同的形状,这些形状也是流程图中常用的形状,有了这些基础,用户可按自己的需求添加自己的形状。当然现在的形状属性还很少,会随着课程的深入而丰富,以支持更多效果。</p>
<p>我们还抽象出一泛型方法来简化添加形状的操作,使用起来很是优雅。</p>
<p>现在基本的形状都有了,我们下节课就来添加新的连线:贝塞尔曲线,这个几乎是最常见的曲线。</p>
<p>同时,有了新的连线,我们还会增加不同的连接点用来连线,而不再只连接形状的中心点。</p>
<p>敬请期待。</p>
<p>感谢大家的观看,本人水平有限,文章不足之处欢迎大家评论指正。</p>
<p>--</p><br><br>
来源:https://www.cnblogs.com/lesliexin/p/18997090
頁: [1]
查看完整版本: [原创]《C#高级GDI+实战:从零开发一个流程图》第08章:增加菱形、平行四边形、圆角矩形,文本居中显示