实现效果:
开发环境:
vs2019
打开sln项目点击运行即可。实现功能有:
保存绘制流程图
加载绘制过的流程图文件
导出图片
画圆
画矩形框
画线段
画曲线
画菱形
画数据库
项目采用了典型的 MVC 模式:
private FlowChartController controller;
- 负责协调视图和模型controller.ViewFactory = new DefaultViewFactory();
controller.ViewFactory = new GreyViewFactory();
- 使用工厂模式创建视图项目支持多种流程图元素:
代码实现了完善的事件处理机制:
controller.Resize += new Action(controller_Resize);
controller.Selected += new Action
controller.Changed += new Action
controller.OnFlowChartChange += new Action(controller_OnFlowChartChange);
提供了两种存储方式:
// 文件存储
new FileStorage(saveFileDialog1.FileName).Save(controller.Model);
// 图片导出
new ImageStorage(saveFileDialog2.FileName).Save(controller.Model);
propertyGrid1.SelectedObject = obj;
使用 PropertyGrid 进行组件属性编辑
优点:
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