版权全部,引用请注明出处:<<http://www.cnblogs.com/dragon/p/5203663.html >>html
本文所用示例下载FlowChart.zip算法
一个用Netron开发的实际应用请看:发布一个免费开源软件-- PAD流程图绘制软件PADFlowChartide
1、 概述工具
Netron是一个开源的图形开发库,它还有一个轻量级的版本叫NetronLight,本文不讨论NetronLight。动画
在NetronGraphLib里,须要重点理解的是四个类,这四个类理解了,NetonGraphLib就掌握了大半部分:this
Shape、Connector、Connection共通的一些属性:spa
2、 GraphControl控件设计
将NetronGraphLib添加到解决方案3d
打开一个Form设计窗口,这时你会看到在工具箱里多了一个NetronGraphLib组件面板
将面板里的GraphControl组件拖到Form窗体上,以下图
GraphControl组件是用来画图的画布,并且这个组件能够自动帮咱们管理图形的移动,变形,选择等操做。
下面咱们看看该组件的一些属性设置
滚动条
AutoScroll=True:设置当图形超出画布边界时滚动条是否会自动显现
RestrictToCanvas=False:设置图形是否能够被移动到超出当前画布的位置
这两个属性配合起来能够实现将图形拖动到超出当前画布位置时画布自动出现滚动条
好比下面的图形显示在GraphControl上,
当拖动图形到画布以外时,滚动条自动出现了
停靠
一般状况下咱们都会设置Dock属性为Full,通常状况下没有什么问题,可是当你在窗体下方添加了状态条,而又设置了滚动条时,会出现水平方向的滚动条被状态条挡住显示不了的问题。为了解决这个问题,须要将Dock设置为None,同时设置Anchor为Top,Bottom,Left,Right.这样就能够达到Dock为Full时同样的效果,同时状态条也能正常显示。
EnableLayout=False:这个属性若是选择True,则画布会在你拖动两个相互链接的图形时出现动画效果,可是你没法控制图形最后中止的位置
当EnableLayout=True的时候GraphLayoutAlgorithm能够设定产生动画效果的算法
最后,咱们把GraphControl控件的Name属性设为graphControl,方便在代码中引用
GraphControl的其它经常使用属性和方法:
3、 开发图形:Shape
咱们打算画一个矩形图形,以下图
一、 创建一个SequenceShape对象,从Shape类继承
using Netron.GraphLib; public class SequenceShape : Shape{ }
二、 改写Shape的InitEntity方法
咱们须要在该方法中初始化对象的矩形坐标,以及设定画边框的画笔Pen和Shape的背景颜色ShapeColor,
protected override void InitEntity() { base.InitEntity(); Pen = new Pen(Color.FromArgb(167, 58, 95)); ShapeColor = Color.FromArgb(255, 253, 205); Rectangle = new RectangleF(100,10 0, 100, 50); }
不要忘了先调用基类的InitEntity方法
3、改写Shape的Paint方法,进行具体的画图
public override void Paint(Graphics g) { base.Paint(g); g.FillRectangle(new SolidBrush(ShapeColor), Rectangle); g.DrawRectangle(Pen, System.Drawing.Rectangle.Round(Rectangle)); }
FillRectangle方法是用来填充矩形的背景色,DrawRectangle是用来画矩形的边框。至于为何要先填背景色再画边框,是由于先画边框会致使边框有两条边被背景色覆盖掉。
4、在画布上显示图形
要在画布上显示图形,只需生成Shape实例并加入画布对象graphControl
咱们在点击工具栏btn_sequence按钮的事件中加入生成SequenceShape实例的代码
private void btn_sequence_Click(object sender, EventArgs e) { SequenceShape shape = new SequenceShape(); graphControl.AddShape(shape); }
运行程序,下面是效果图
点击btn_sequence,在graphControl画布上就能够画出矩形图形了,并且还具备了选中、移动、改变大小等功能,这些功能都是由graphControl对象自动管理的。
4、 增长图形对象的功能
一、 让矩形图形显示文本
在InitEntity()方法中添加下面代码
protected override void InitEntity() { …… Text = "顺序图形"; Font = new Font("宋体", 10); …… }
在Paint()方法中添加画出文本的代码
public override void Paint(Graphics g) { …… if (!string.IsNullOrEmpty(Text)) g.DrawString(Text,this.Font, this.TextBrush,System.Drawing.RectangleF.Inflate(Rectangle,0,-2)); }
下面是效果图
二、 实现双击图形修改图形中的文本
这个功能也是画图软件常见的功能,咱们须要在Shape的鼠标双击事件中建立一个TextBox控件,而后将图形的文本传给TextBox控件,同时添加相应TextBox的LostFocus事件的代码,使TextBox消失
在InitEntity()方法中添加挂钩OnMouseDown事件的代码
protected override void InitEntity() { …… OnMouseDown += SequenceShape_OnMouseDown; }
而后在SequenceShape_OnMouseDown中生成TextBox控件并显示
private void SequenceShape_OnMouseDown(object sender, MouseEventArgs e) { if (e.Clicks == 2 && e.Button == MouseButtons.Left) { if (m_tb == null) { m_tb = new TextBox(); } m_tb.Location = System.Drawing.Point.Round(Rectangle.Location); m_tb.Width = (int) Rectangle.Width; m_tb.Height = (int)Rectangle.Height; m_tb.BackColor = ShapeColor; m_tb.Multiline = true; m_tb.Text = Text; m_tb.SelectionLength = Text.Length; m_tb.LostFocus += T_tb_LostFocus; (Site as Control).Controls.Add(m_tb); m_tb.Show(); m_tb.Focus(); m_tb.ScrollToCaret(); } } private void T_tb_LostFocus(object sender, EventArgs e) { Text = m_tb.Text; m_tb.Hide(); (Site as Control).Controls.Remove(m_tb); }
Shape.Site就是GraphControl对象在Shape中的引用,在调用GraphControl.AddShape()时会设置Shape.Site。只是在Shape中的Site类型是IGraphSite类型,而GraphControl对象则是继承了System.Windows.Forms .ScrollableControl, IGraphSite接口和 IGraphLayout接口
另外要说明的一点是,在NetronGraphLib中,Shape对象的OnMouseDown是经过GraphControl. OnMouseDown来调用的,而在NetronGraphLib原来的设计中,鼠标双击事件被GraphControl. OnMouseDown检测到后,会显示图形对象的属性,然而并无继续调用Shape.OnMouseDown,因此Shape永远接收不到鼠标双击事件。而笔者在所附代码里修复了这个问题,将鼠标双击事件继续传递给Shape
三、 Shape类的一些经常使用方法和数据成员
5、 链接图形:Connector和Connection
有了Shape图形对象后,咱们要在图形对象之间画链接线。
一、 添加链接点
首先,咱们要给图形对象添加链接点Connector
给SequenceShape添加下面的Connector数据成员
private Connector m_leftConnector; private Connector m_rightConnector;
在InitEntity()方法中添加对Connector数据成员的初始化代码
protected override void InitEntity() { …… m_leftConnector = new Connector(this,"Left",true); Connectors.Add(m_leftConnector); m_rightConnector = new Connector(this, "Right", true); Connectors.Add(m_rightConnector); …… }
Connector构造函数的第一个参数是Shape对象,表明的Connector对象依附的图形对象
第二个参数是Connector的名字,这个名字比较重要,当你要从其它对象访问该Connector时,就能够用SequenceShape.Connectors[“Left”]来访问到m_leftConnector了
第三个参数表示是否容许Connector有多个链接
二、 重写Shape的ConnectionPoint方法:
咱们还要重写Shape的ConnectionPoint方法,返回每个Connector的具体坐标。由于当你在代码中用Connector.Location查询Connector的坐标时,它就是经过查询本身所依附的Shape的ConnectionPoint方法来返回本身的坐标值的。经过Connector.BelongsTo能够获得Connector所依附的Shape对象。
public override PointF ConnectionPoint(Connector c) { if (c == m_leftConnector) { return new PointF(Rectangle.Left, Rectangle.Top + Rectangle.Height / 2); } if (c == m_rightConnector) { return new PointF(Rectangle.Right, Rectangle.Top + Rectangle.Height / 2); } return new PointF(0, 0); }
这时咱们再运行程序,把鼠标移到Shape上,就能够看到Shape已经有了左右两个链接点;当鼠标移动到链接点上方时,鼠标会变成一个小的绿色方块,表示如今能够经过按下鼠标并拖动来画一条链接线Connection。这些功能都是经过GraphControl的OnMouseDown, OnMouseMove, OnMouseUp里的代码自动实现的。咱们在后续篇章中还要讨论GraphControl里关于处理鼠标事件的代码。
三、 Connector的经常使用属性和方法
AdjacentPoint属性表示的是Connector向着Shape图形对象外延伸ConnectionShift距离的一个点。若是ConnectionLocation是North,则AdjacentPoint是从Connector所在坐标点向正上方延伸出的一个点;若是是East则是向右方延伸出的一个点。若是是Omni则忽略ConnecionShift,AdjacentPoint就是Connector自身所在的坐标点。
在用系统提供的Connection链接图形对象时,Connection的Paint方法先从From Connector所在坐标点向AdjacentPoint画一条直线,而后再画直线到To Connector的AdjacentPoint,最后从AdjacentPoint再画直线到To Connector的坐标点。
6、 自定义Connection类
若是Connection定义的画法不能知足你的需求,你就须要设计本身的Connection对象。可是NetronGraphLib并不能很好的支持自定义的Connection,在NetronGraphLib的代码里,是在Connection. LinePath属性的设置代码中,给Connection.ConnectionPainter和Connection.Tracker设置不一样的对象,从而决定如何画出Connection。但是若是你想加入本身的Painter和Tracker,就必须修改Connection.LinePath属性的设置方法。这恐怕不是一个好的解决方法。
因此为了可以方便的加入本身设计的Connection,笔者修改了GraphControl.OnMouseDown里的代码。由于在这段代码里设定了当用户在一个Connector上按下鼠标时,会生成一个Connection对象。
我作的修改以下:
在NetronGraphLib中定义一个IConnectable接口,这个接口定义了一个方法Connection CreateConnection(Connector connector),用来根据鼠标点击的Connector以及Connector依附的Shape来产生一个Connection对象。
而后在OnMouseDown里检查当前鼠标按下的Connector依附的Shape是否实现了IConnectable接口,若是实现,则调用CreateConnection方法来返回一个Connection对象。由于一般在用户按下鼠标点击Connector的时候,就能够根据点击的Shape和Shape上具体哪一个Connector返回不一样的Connection了。
因此如今若是要实现你本身的Connetion,你须要作的就是
一、 实现自定义链接线
咱们打算实现以下图所示的链接线,当图形右侧的链接点拖动到下一个图形左侧链接点时,链接线始终呈现为一条水平线和垂直线;而当从图形左侧的链接点拖动到下一个图形左侧的链接点时,链接线始终呈现为一条垂直线
咱们先定义本身的链接线类
public class FlowChartConnection : Connection{}
而后咱们覆写Connection类的GetConnectionPoints方法,该方法返回Connection链接线上的多个点,Connection.Paint方法调用GetConnectionPoints来画出一条折线
public override PointF[] GetConnectionPoints() { PointF[] points; PointF t_to = PointF.Empty; PointF t_from = Point.Empty; if (From == null) return null; if (From?.Name == "Left" && To?.Name == "Left") { //若是是左侧点链接左侧点,则返回垂直线 //To?.Name中的?表示会先检查To是否为null points = new PointF[2]; points[0] = From.Location; points[1] = new PointF(From.Location.X, To.Location.Y); return points; } points = new PointF[3]; t_from = From.Location; t_to = (To != null) ? To.Location : ToPoint; points[0] = t_from; points[1] = new PointF(t_to.X, t_from.Y); points[2] = t_to; return points; }
要说明的几点:
a) From、To都是Connector类型,无论用户是从左到右仍是从右到左拖动,From指的都是鼠标按下开始拖动出Connection链接线时的Connector,To都是放开鼠标时所在的Connector;
b) 在拖动的过程当中,To是空值null,这时ToPoint属性指示了拖动过程当中鼠标所在位置
c) 在NetronGraphLib中有个Bug,在Connection的构造函数里,调用了InitConnection()方法,而InitConnection方法中生成了DefaultPainter对象,DefaultPainter构造函数调用基类ConnectionPainter的构造函数时调用了Connection .GetConnectionPoints()方法。而在此时Connection.From尚未赋值。因此若是你覆写GetConnectionPoints时没有检查From是否为空值,系统就会抛出异常。我去掉了ConnectionPainter构造函数调用GetConnectionPoints()方法的代码,这样能够保证GetConnectionPoints被调用时From不为空值
若是你还有别的需求,能够进一步覆写Paint方法,本身编写Connection的画法
接下来咱们要使SequenceShape继承IConnectable接口并实现CreateConnection方法返回咱们自定义的FlowChartConnection
using Netron.GraphLib.Interfaces;
public class SequenceShape : Shape, IConnectable
public Connection CreateConnection(Connector connector) { return new FlowChartConnection(); }
固然你也能够根据传入的connector参数决定返回不一样的Connection对象
二、 Connection的经常使用属性和方法
Connection connection = new Connection(); connection.Insert(fromConnector, toConnector);