基于C# winform实现简单流程图绘制工具

 实现效果:

基于C# winform实现简单流程图绘制工具_第1张图片

开发环境:
vs2019
打开sln项目点击运行即可。实现功能有:
保存绘制流程图
加载绘制过的流程图文件
导出图片
画圆
画矩形框
画线段
画曲线
画菱形
画数据库 

1. 项目架构

项目采用了典型的 MVC 模式:

  • Model: FlowChart.Models 命名空间
  • View: FlowChart.Views.Default 和 FlowChart.Views.Grey 两种视图实现
  • Controller: FlowChart.Controller 命名空间中的 FlowChartController

2. 主要组件

  • 控制器

    private FlowChartController controller;

    - 负责协调视图和模型
  • 处理用户交互
  • 管理撤销缓冲区
  • 控制缩放功能
  • 视图工厂

    controller.ViewFactory = new DefaultViewFactory();

    controller.ViewFactory = new GreyViewFactory();

    - 使用工厂模式创建视图
  • 支持多种主题切换(Default和Grey)
  • 基础组件

项目支持多种流程图元素:

  • RectangleComponent (矩形/处理框)
  • RoundComponent (圆形/终止符)
  • LineComponent (直线连接器)
  • CurvedLineComponent (曲线连接器)
  • RhombusComponent (菱形/判断框)
  • DatabaseComponent (数据库符号)

3. 事件处理系统

代码实现了完善的事件处理机制:

controller.Resize += new Action(controller_Resize);

controller.Selected += new Action(controller_OnSelected);

controller.Changed += new Action(controller_OnChange);

controller.OnFlowChartChange += new Action(controller_OnFlowChartChange);

4. 持久化功能

提供了两种存储方式:

// 文件存储

new FileStorage(saveFileDialog1.FileName).Save(controller.Model);

// 图片导出

new ImageStorage(saveFileDialog2.FileName).Save(controller.Model);

5. 用户界面功能

  • 基本操作
  • 添加各种图形组件
  • 缩放控制
  • 主题切换
  • 页面布局(横向/纵向)
  • 属性编辑

    propertyGrid1.SelectedObject = obj;

使用 PropertyGrid 进行组件属性编辑

6. 设计模式应用

  • MVC模式 - 整体架构
  • 工厂模式 - 视图创建
  • 观察者模式 - 事件处理系统
  • 策略模式 - 不同的存储实现

代码质量分析

优点:

1. 结构清晰,职责分明

  • 使用了合适的设计模式
  • 提供了完善的功能支持
  • 实现了良好的扩展性

程序主界面代码:

using System;
using System.ComponentModel;
using System.Windows.Forms;
using FlowChart.Models;
using FlowChart.Controller;
using FlowChart.Entities;
using FlowChart.Persistance;
using FlowChart.Views.Default;
using FlowChart.Views.Grey;

namespace FlowChart
{
    public partial class FormMain : Form
    {
        private FlowChartController controller;
        public FormMain()
        {
            InitializeComponent();
            controller = new FlowChartController(flowChartPage1, new GreyViewFactory());
            flowChartContainerBindingSource.DataSource = controller.GetUndoBuffers();
            controller.ViewFactory = new DefaultViewFactory();
            panel1.Resize+=new EventHandler(panel1_Resize);            

            
            controller.Resize += new Action(controller_Resize);
            controller.Selected += new Action(controller_OnSelected);
            controller.Changed += new Action(controller_OnChange);
            controller.OnFlowChartChange += new Action(controller_OnFlowChartChange);
            
            propertyGrid1.SelectedGridItemChanged += new SelectedGridItemChangedEventHandler(propertyGrid1_SelectedGridItemChanged);
            propertyGrid1.Leave += new EventHandler(propertyGrid1_Leave);
            undoGrid.CellContentClick += new DataGridViewCellEventHandler(undoGrid_CellContentClick);
            lstAllObjects.SelectedIndexChanged += new EventHandler(lstAllObjects_SelectedIndexChanged);

            potraitToolStripMenuItem_Click(this, null);
        }
        
        void lstAllObjects_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (lstAllObjects.SelectedItems.Count > 0)
            {
                controller.SelectedComponent = lstAllObjects.SelectedItems[0].Tag as BaseComponent;
            }
        }

        void undoGrid_CellContentClick(object sender, DataGridViewCellEventArgs e)
        {
            if (e.ColumnIndex == 1 && e.RowIndex >= 0 && e.RowIndex < undoGrid.Rows.Count)
            {
                var fc = undoGrid.Rows[e.RowIndex].DataBoundItem as FlowChartContainer;
                if (fc != null)
                {
                    controller.Load(fc);
                }
            }
        }

        void controller_OnFlowChartChange()
        {
            lstAllObjects.Items.Clear();
            
            controller.Model.Items.ForEach(x => {
                var lv = new ListViewItem(x.Text) { 
                    Tag = x, 
                    ImageIndex = x.ImageIndex 
                };
                lstAllObjects.Items.Add(lv);
            });
            
            flowChartContainerBindingSource.ResetBindings(false);
        }

        void propertyGrid1_Leave(object sender, EventArgs e)
        {
            controller.PropertyChanged();
        }

        void propertyGrid1_SelectedGridItemChanged(object sender, SelectedGridItemChangedEventArgs e)
        {
            flowChartPage1.Invalidate();            
        }

        void controller_OnChange(BaseComponent obj)
        {
            if (obj != null && obj.SelectedPoint != null)
            {
                txtStatusText.Text = string.Format("{0} - {1}", obj.SelectedPoint.X, obj.SelectedPoint.Y);
            }
            propertyGrid1.SelectedObject = obj;
        }

        void controller_OnSelected(BaseComponent obj)
        {
            propertyGrid1.SelectedObject = obj;
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            panel1_Resize(sender, e);
        }

        private void btnBox_Click(object sender, EventArgs e)
        {
            controller.Add(new RectangleComponent() {
                Text = "Process",  
                TopLeftCorner = new FlowChartPoint(50, 50), 
                BottomRightCorner = new FlowChartPoint(100, 100) });
        }

        private void btnCircle_Click(object sender, EventArgs e)
        {
            controller.Add(new RoundComponent()
            { 
                Text = "Terminator", 
                TopLeftCorner = new FlowChartPoint(50, 50), 
                BottomRightCorner = new FlowChartPoint(100, 100) 
            });
        }

        private void btnLine_Click(object sender, EventArgs e)
        {
            controller.Add(new LineComponent()
            {
                Text = "Connector", 
                StartPoint = new FlowChartPoint(100, 100), 
                EndPoint = new FlowChartPoint(200, 200) 
            });
        }

        private void btnCurvedLine_Click(object sender, EventArgs e)
        {
            controller.Add(new CurvedLineComponent()
            {
                Text="Connector", 
                StartPoint = new FlowChartPoint(200, 200), 
                EndPoint = new FlowChartPoint(200, 100), 
                ControlPoint2 = new FlowChartPoint(100, 100), 
                ControlPoint1 = new FlowChartPoint(100, 200) 
            });
        }

        private void btnRhombus_Click(object sender, EventArgs e)
        {
            controller.Add(new RhombusComponent()
            {
                Text = "Decision",
                TopLeftCorner = new FlowChartPoint(50, 50),
                BottomRightCorner = new FlowChartPoint(100, 100)
            });
        }

        private void btnDatabase_Click(object sender, EventArgs e)
        {
            controller.Add(new DatabaseComponent()
            {
                Text = "Database",
                TopLeftCorner = new FlowChartPoint(50, 50),
                BottomRightCorner = new FlowChartPoint(100, 100)
            });
        } 
        
        private void panel1_Resize(object sender, EventArgs e)
        {
            float offSet = 10;
            controller.ResetView(
                offSet, 
                panel1.Height - flowChartPage1.Height - offSet, 
                offSet, 
                panel1.Width - flowChartPage1.Width - offSet);

            controller_Resize();
        }

        void controller_Resize()
        {
            int left = (int)((panel1.Width - flowChartPage1.Width) / 2.0f);
            if (left > 4)
            {
                flowChartPage1.Left = left;
            }            
        }

        private void btnSave_Click(object sender, EventArgs e)
        {
            saveFileDialog1.ShowDialog();
        }

        private void saveFileDialog1_FileOk(object sender, CancelEventArgs e)
        {
            new FileStorage(saveFileDialog1.FileName).Save(controller.Model);            
        }

        private void openFileDialog1_FileOk(object sender, CancelEventArgs e)
        {
            controller.Load(new FileStorage(openFileDialog1.FileName).Load());
        }

        private void btnOpen_Click(object sender, EventArgs e)
        {
            openFileDialog1.ShowDialog();
        }

        private void btnExport_Click(object sender, EventArgs e)
        {
            saveFileDialog2.ShowDialog();
        }

        private void saveFileDialog2_FileOk(object sender, CancelEventArgs e)
        {            
            new ImageStorage(saveFileDialog2.FileName).Save(controller.Model);           
        }

        private void btnZoomIn_Click(object sender, EventArgs e)
        {
            controller.ZoomFactor*=1.5f;            
            panel1_Resize(sender, e);
        }

        private void btnZoomOut_Click(object sender, EventArgs e)
        {
            controller.ZoomFactor /= 1.5f;            
            panel1_Resize(sender, e);
        }

        private void btnFit_Click(object sender, EventArgs e)
        {
            controller.ZoomFactor = 1.0f;
            panel1_Resize(sender, e);
        }

        private void ddlTheme_SelectedIndexChanged(object sender, EventArgs e)
        {
            controller.ViewFactory = new GreyViewFactory();
        }

        private void potraitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.flowChartPage1.Width = 601;
            this.flowChartPage1.Height = 851;
        }

        private void landscapeToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.flowChartPage1.Width = 851;
            this.flowChartPage1.Height = 601;
        }
    }
}

绘制折线:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.ComponentModel;
using FlowChart.Entities;
using FlowChart.Utility;
using FlowChart.Views;

namespace FlowChart.Models
{
    public class CurvedLineComponent:BaseLineComponent
    {
        #region Properties
        [TypeConverter(typeof(FlowChartPointConverter))]
        [DescriptionAttribute("Control point 1 for the bezier curve")]
        public FlowChartPoint ControlPoint1 { get; set; }
        
        [TypeConverter(typeof(FlowChartPointConverter))]
        [DescriptionAttribute("Control point 2 for the bezier curve")]
        public FlowChartPoint ControlPoint2 { get; set; }
        #endregion

        
        #region Private
        public List points = new List();
        private PointF Bezier(FlowChartPoint a, FlowChartPoint b, FlowChartPoint c, FlowChartPoint d, float t)
        {
            FlowChartPoint
                ab = new FlowChartPoint(),
                bc = new FlowChartPoint(),
                cd = new FlowChartPoint(),
                abbc = new FlowChartPoint(),
                bccd = new FlowChartPoint(),
                dest = new FlowChartPoint();
            Lerp( ab,  a,  b, t);           // point between a and b (green)
            Lerp( bc,  b,  c, t);           // point between b and c (green)
            Lerp( cd,  c,  d, t);           // point between c and d (green)
            Lerp( abbc,  ab,  bc, t);       // point between ab and bc (blue)
            Lerp( bccd,  bc,  cd, t);       // point between bc and cd (blue)
            Lerp( dest,  abbc,  bccd, t);   // point on the bezier-curve (black)
            return dest.MakePointF();
        }
        void Lerp(FlowChartPoint dest, FlowChartPoint a, FlowChartPoint b, float t)
        {
            dest.X = a.X + (b.X - a.X) * t;
            dest.Y = a.Y + (b.Y - a.Y) * t;
        }
        public override void RecomputePoints()
        {
            if (!ParentMoving)
            {
                points.Clear();
                for (float t = 0; t < 1; t += 0.001f)
                {
                    points.Add(Bezier(StartPoint, ControlPoint1, ControlPoint2, EndPoint, t));
                }
            }
            base.RecomputePoints();
        }
        #endregion

        #region Virtual
        public override void Init()
        {
            RecomputePoints();
            this.ImageIndex = 4;
            base.Init();
        }
        public override void Accept(Visitors.BaseVisitor visitor)
        {            
            visitor.Visit(this);            
        }
        
        public override FlowChartComponent GetComponent()
        {
            FlowChartComponent component = base.GetComponent();
            component.Points.Add(ControlPoint1);
            component.Points.Add(ControlPoint2);
            return component;
        }

        public override FlowChartPoint GetWeightedPoint()
        {
            FlowChartPoint pt = StartPoint.CloneAndAdd(EndPoint).CloneAndAdd(ControlPoint1).CloneAndAdd(ControlPoint2);
            pt.X = pt.X / 4;
            pt.Y = pt.Y / 4;
            return pt;
        }

        public override void SetComponent(FlowChartComponent component)
        {            
            ControlPoint1 = component.Points[2];
            ControlPoint2 = component.Points[3];
            base.SetComponent(component);
        }
        public override void OnParentMoved(BaseBoxComponent box, bool recompute)
        {
            if (this.ConnectionStart == box && this.ConnectionEnd == box)
            {
                this.ControlPoint1.Add(box.EdgePoints[this.ConnectionStartPointIndex].CloneAndSubtract(this.StartPoint));
                this.ControlPoint2.Add(box.EdgePoints[this.ConnectionStartPointIndex].CloneAndSubtract(this.StartPoint));
            }
            base.OnParentMoved(box, recompute);
        }        

        public override bool HitTest(int x, int y)
        {
            if (GraphicsUtil.Distance(ControlPoint1, (float)x, (float)y) < View.ViewFactory.EdgeBoxWidth / 2)
            {
                this.SelectedPoint = ControlPoint1;
                this.MouseState = Entities.MouseState.Resize;
                return true;
            }
            if (GraphicsUtil.Distance(ControlPoint2, (float)x, (float)y) < View.ViewFactory.EdgeBoxWidth / 2)
            {
                this.SelectedPoint = ControlPoint2;
                this.MouseState = Entities.MouseState.Resize;
                return true;
            }
            if (base.HitTest(x, y))
            {
                return true;
            }
            else
            {
                if (GraphicsUtil.HasPoint(new FlowChartPoint[] { StartPoint, ControlPoint1, ControlPoint2, EndPoint }, x, y) ||
                    GraphicsUtil.HasPoint(new FlowChartPoint[] { StartPoint, ControlPoint1, EndPoint, ControlPoint2 }, x, y))
                {
                    for (int i = 0; i < this.points.Count; i++)
                    {
                        if (GraphicsUtil.Distance(x, y, this.points[i].X, this.points[i].Y) < View.ViewFactory.EdgeBoxWidth / 2)
                        {
                            this.MouseState = Entities.MouseState.Move;
                            this.LastHitPoint.X = x;
                            this.LastHitPoint.Y = y;
                            this.SelectedPoint = this.LastHitPoint;
                            return true;
                        }
                    }                    
                }
                return false;
            }            
        }
        public override void MouseMove(System.Windows.Forms.MouseEventArgs e)
        {
            RecomputePoints();
            base.MouseMove(e);            
        }
        public override void Move(System.Windows.Forms.MouseEventArgs e)
        {
            if (this.SelectedPoint != null)
            {
                ControlPoint1.X += e.X - this.SelectedPoint.X;
                ControlPoint1.Y += e.Y - this.SelectedPoint.Y;

                ControlPoint2.X += e.X - this.SelectedPoint.X;
                ControlPoint2.Y += e.Y - this.SelectedPoint.Y;

                base.Move(e);
                RecomputePoints();
            }
        }
        #endregion
    }
}

绘制线段:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using FlowChart.Entities;
using FlowChart.Utility;
using FlowChart.Views;

namespace FlowChart.Models
{
    public class LineComponent:BaseLineComponent
    {
        #region Virtual
        public override void Init()
        {
            this.ImageIndex = 3;
            base.Init();
        }
        public override void Accept(Visitors.BaseVisitor visitor)
        {
            visitor.Visit(this);
        }        

        public override bool HitTest(int x, int y)
        {
            if (base.HitTest(x, y))
            {
                return true;
            }
            else
            {
                if (GraphicsUtil.HasPoint(StartPoint.MakeFlowChartPointArrayWith(EndPoint), x, y))
                {
                    if (GraphicsUtil.DistanceToLine(StartPoint, EndPoint, x, y) < View.ViewFactory.EdgeBoxWidth)
                    {
                        this.MouseState = Entities.MouseState.Move;
                        this.LastHitPoint.X = x;
                        this.LastHitPoint.Y = y;
                        this.SelectedPoint = this.LastHitPoint;
                        return true;
                    }
                }
                return false;
            }            
        }
        
        #endregion
    }
}

绘制矩形:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;
using FlowChart.Entities;
using FlowChart.Utility;
using FlowChart.Views;

namespace FlowChart.Models
{
    public class RectangleComponent: BaseBoxComponent
    {
        #region Constructor
        public RectangleComponent()
        {
            this.ImageIndex = 0;
            for (int i = 0; i < 12; i++)
            {
                this.EdgePoints.Add(new FlowChartPoint());
            }
        }
        #endregion

        #region Virtual
        public override void Accept(Visitors.BaseVisitor visitor)
        {
            visitor.Visit(this);
        }
        
        public override void RecomputeEdgePoints()
        {
            int i = 0;
            //Top Side
            this.EdgePoints[i].X = TopLeftCorner.X + this.Width * 0.25f;
            this.EdgePoints[i++].Y = TopLeftCorner.Y;

            this.EdgePoints[i].X = TopLeftCorner.X + this.Width * 0.5f;
            this.EdgePoints[i++].Y = TopLeftCorner.Y;

            this.EdgePoints[i].X = TopLeftCorner.X + this.Width * 0.75f;
            this.EdgePoints[i++].Y = TopLeftCorner.Y;

            //Right Side
            this.EdgePoints[i].X = BottomRightCorner.X;
            this.EdgePoints[i++].Y = TopLeftCorner.Y + this.Height * 0.25f;

            this.EdgePoints[i].X = BottomRightCorner.X;
            this.EdgePoints[i++].Y = TopLeftCorner.Y + this.Height * 0.5f;

            this.EdgePoints[i].X = BottomRightCorner.X;
            this.EdgePoints[i++].Y = TopLeftCorner.Y + this.Height * 0.75f;

            //Bottom Side     
            this.EdgePoints[i].X = TopLeftCorner.X + this.Width * 0.25f;
            this.EdgePoints[i++].Y = BottomRightCorner.Y;

            this.EdgePoints[i].X = TopLeftCorner.X + this.Width * 0.5f;
            this.EdgePoints[i++].Y = BottomRightCorner.Y;

            this.EdgePoints[i].X = TopLeftCorner.X + this.Width * 0.75f;
            this.EdgePoints[i++].Y = BottomRightCorner.Y;

            //Left Side
            this.EdgePoints[i].X = TopLeftCorner.X;
            this.EdgePoints[i++].Y = TopLeftCorner.Y + this.Height * 0.25f;
            
            this.EdgePoints[i].X = TopLeftCorner.X;
            this.EdgePoints[i++].Y = TopLeftCorner.Y + this.Height * 0.5f;

            this.EdgePoints[i].X = TopLeftCorner.X;
            this.EdgePoints[i++].Y = TopLeftCorner.Y + this.Height * 0.75f;
        }

        public override FlowChartComponent GetComponent()
        {
            FlowChartComponent component = new FlowChartComponent();
            component.ID = this.ID;
            component.Text = Text;
            component.Type = this.GetType().FullName;
            component.Points.Add(TopLeftCorner);
            component.Points.Add(BottomRightCorner);            
            return component;
        }

        public override void SetComponent(FlowChartComponent component)
        {
            ID = component.ID;
            Text = component.Text;
            TopLeftCorner = component.Points[0];
            BottomRightCorner = component.Points[1];
        }
        #endregion
    }
}

绘制菱形:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing.Drawing2D;
using System.Drawing;
using FlowChart.Entities;
using FlowChart.Utility;
using FlowChart.Views;

namespace FlowChart.Models
{
    public class RhombusComponent:BaseBoxComponent
    {
        private PointF[] coordinates = new PointF[4];
        #region Constructor
        public RhombusComponent()
        {
            this.ImageIndex = 2;
            for (int i = 0; i < 4; i++)
            {
                this.EdgePoints.Add(new FlowChartPoint());
            }
        }
        #endregion

        #region Virtual
        public override void Accept(Visitors.BaseVisitor visitor)
        {
            visitor.Visit(this);
        }
        
        public override void RecomputeEdgePoints()
        {
            this.EdgePoints[0].X = TopLeftCorner.X + this.Width / 2;
            this.EdgePoints[0].Y = TopLeftCorner.Y;

            this.EdgePoints[1].X = BottomRightCorner.X;
            this.EdgePoints[1].Y = TopLeftCorner.Y + this.Height / 2;

            this.EdgePoints[2].X = TopLeftCorner.X + this.Width / 2;
            this.EdgePoints[2].Y = BottomRightCorner.Y;

            this.EdgePoints[3].X = TopLeftCorner.X;
            this.EdgePoints[3].Y = TopLeftCorner.Y + this.Height / 2;
        }

        public override FlowChartComponent GetComponent()
        {
            FlowChartComponent component = new FlowChartComponent();
            component.ID = this.ID;
            component.Text = Text;
            component.Type = this.GetType().FullName;
            component.Points.Add(TopLeftCorner);
            component.Points.Add(BottomRightCorner);            
            return component;
        }

        public override void SetComponent(FlowChartComponent component)
        {
            ID = component.ID;
            Text = component.Text;
            TopLeftCorner = component.Points[0];
            BottomRightCorner = component.Points[1];
        }

        #endregion
    }
}

绘制圆:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing.Drawing2D;
using System.Drawing;
using FlowChart.Entities;
using FlowChart.Utility;
using FlowChart.Views;

namespace FlowChart.Models
{
    public class RoundComponent:BaseBoxComponent
    {
        #region Constructor
        public RoundComponent()
        {            
            this.ImageIndex = 1;
            for (int i = 0; i < 8; i++)
            {
                this.EdgePoints.Add(new FlowChartPoint());
            }
        }
        #endregion

        #region Virtual
        public override void Accept(Visitors.BaseVisitor visitor)
        {
            visitor.Visit(this);
        }
        
        public override void RecomputeEdgePoints()
        {
            float aradius = Math.Abs(TopLeftCorner.MakeRectangleFTill(BottomRightCorner).Width)/2;
            float bradius = Math.Abs(TopLeftCorner.MakeRectangleFTill(BottomRightCorner).Height) / 2;
            FlowChartPoint centerPoint = TopLeftCorner.CloneAndAdd(BottomRightCorner);
            centerPoint.X /= 2;
            centerPoint.Y /= 2;
            float angle = -(float)Math.PI / 2.0f;
            this.EdgePoints.ForEach(x => {
                x.X = centerPoint.X + aradius * (float)Math.Cos(angle);
                x.Y = centerPoint.Y + bradius * (float)Math.Sin(angle);
                angle += (float)Math.PI / 4.0f;
            });
        }

        public override FlowChartComponent GetComponent()
        {
            FlowChartComponent component = new FlowChartComponent();
            component.ID = this.ID;
            component.Text = Text;
            component.Type = this.GetType().FullName;
            component.Points.Add(TopLeftCorner);
            component.Points.Add(BottomRightCorner);            
            return component;
        }

        public override void SetComponent(FlowChartComponent component)
        {
            ID = component.ID;
            Text = component.Text;
            TopLeftCorner = component.Points[0];
            BottomRightCorner = component.Points[1];
        }

        #endregion
    }
}

代码 pan.baidu.com/s/1iVXbDy8kSaYl_Jvh4tNotg 提取: 19dg 

你可能感兴趣的:(C#,c#,流程图,开发语言)