Netron开发快速上手(一):GraphControl,Shape,Connector和Connection

版权全部,引用请注明出处:<<http://www.cnblogs.com/dragon/p/5203663.html >>html

本文所用示例下载FlowChart.zip算法

一个用Netron开发的实际应用请看:发布一个免费开源软件-- PAD流程图绘制软件PADFlowChartide

Netron 2.2原版代码下载函数

1、      概述工具

Netron是一个开源的图形开发库,它还有一个轻量级的版本叫NetronLight,本文不讨论NetronLight。动画

在NetronGraphLib里,须要重点理解的是四个类,这四个类理解了,NetonGraphLib就掌握了大半部分:this

  • GraphControl:表明的是画布对象,全部的图形对象都是在画布上展示,同时画布对象管理着图形对象的各类行为,如拖动,变形,选中以及链接等。也能够经过画布对象访问到全部的图形对象。
  • Shape:表明的是一个图形对象
  • Connector:表明的是图形对象上的链接点,两个链接点之间能够产生一条链接线。链接点对象只能依附图形对象而存在。
  • Connection:表明的是两个链接点之间的链接线

 

Shape、Connector、Connection共通的一些属性:spa

  • Site:用来引用GraphControl画布对象
  • IsSelected:代表对象是否处于选中状态
  • IsHovered:鼠标是否正悬停在对象上
  • Paint():用来画出对象的方法
  • Hit():检测对象是否被矩形包含或包含某个坐标点
  • OnMouseDown、OnMouseMove、OnMouseUp:鼠标事件委托,能够捕捉鼠标事件

 

 

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的其它经常使用属性和方法

  • AddShape():能够往画布上添加一个对象并显示
  • Shapes:能够访问graphControl管理的全部图形对象
  • Connections:能够访问全部的Connection对象
  • SelectedShapes:能够访问全部被选中的图形对象
  • Abstract:这个数据成员可能会让人比较困惑,这个Abstract是GraphAbstract类型的对象,其实就是用来管理Shapes、Connections等数据的一个数据类,GraphControl的Shapes成员和Connections成员也是经过访问Abstract来获得的。

3、      开发图形:Shape

咱们打算画一个矩形图形,以下图

 

一、  创建一个SequenceShape对象,从Shape类继承

using Netron.GraphLib;
public class SequenceShape : Shape{  }

  

二、  改写ShapeInitEntity方法

咱们须要在该方法中初始化对象的矩形坐标,以及设定画边框的画笔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、改写ShapePaint方法,进行具体的画图

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类的一些经常使用方法和数据成员

  • 位置信息:X、Y、Location、Left、Right、Width、Height
  • Rectangle:包含Shape的矩形框。要重设Shape的位置,须要从新生成一个Rectangle对象赋值给它,而不能经过改变Rectangle的属性来重设Shape的位置。
  • Location:Shape的左上角坐标点
  • Abstract:能够经过它访问到GraphControl上的全部其余Shape和Connection
  • Connectors:Shape对象上的全部链接点集合
  • IsSelected:用来判断Shape是否处于选中状态
  • Site:对GraphControl画布对象的引用
  • Tracker:ShapeTracker类型的对象,表明的是当Shape被选中的时候在Shape周围画出的表示选中状态的选中框。这个对象的生成是在IsSelected属性里设置的。因此要知道Shape对象有没有被选中,只要查询IsSelected==True或Tracker!=null均可以
  • ShapeMenu():用来返回图形对象的右键菜单

 

5、      链接图形:ConnectorConnection

有了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有多个链接

二、  重写ShapeConnectionPoint方法:

咱们还要重写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的经常使用属性和方法

  • ConnectionGrip():返回一个矩形对象,表明了Connector四周的一个正方形小块,当鼠标移动到这个方块内时,鼠标会变成绿色小方块,表示此时按下鼠标能够拖动出链接线
  • AllowNewConnectionsFrom:false表示该链接点只能接受从其它链接点拖动过来的链接线,而不能从该链接点拖出一条链接线
  • AllowNewConnectionsTo:false时只能拖出不能拖入链接线
  • BelongsTo:Connector所依附的Shape对象
  • Connections:全部和该Connector链接的Connection集合
  • ConnectorLocation、ConnectionShift、AdjacentPoint:这三个属性决定了NetronGraphLib提供的Connection将如何画出。
  • ConnectorLocation属性能够是East, South, West, North, Omni和Unknown。

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,你须要作的就是

  • 从Netron.GraphLib.Connection类继承并设计你本身的Connection类
  • 使你本身设计的Shape类继承IConnectable接口并实现CreateConnection方法,在CreateConnection方法中根据传入的Connector返回你本身的Connection类

 

一、  实现自定义链接线

咱们打算实现以下图所示的链接线,当图形右侧的链接点拖动到下一个图形左侧链接点时,链接线始终呈现为一条水平线和垂直线;而当从图形左侧的链接点拖动到下一个图形左侧的链接点时,链接线始终呈现为一条垂直线

                 

咱们先定义本身的链接线类

    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:
    using Netron.GraphLib.Interfaces;
  • 添加IConnectable接口继承:
    public class SequenceShape : Shape, IConnectable
  • 实现CreateConnection方法:
       public Connection CreateConnection(Connector connector)
       {
           return new FlowChartConnection();
       }

              固然你也能够根据传入的connector参数决定返回不一样的Connection对象

二、  Connection的经常使用属性和方法

  • Insert():这个方法以两个Connector做为参数,将Connection对象加入GraphControl.Connections,以及From Connector和To Connector的Connections集合中。若是你是在代码中本身生成两个链接点之间的Connection,一般你须要作的就是以下两行代码
Connection connection = new Connection();
connection.Insert(fromConnector, toConnector);
  • PaintLabel():咱们有时须要覆写这个方法来为链接线加上文字显示
  • Remove():这个方法不是NetronGraphLib原有的。由于若是你须要本身在代码中生成Connection对象时,一般也会遇到须要删除Connection对象的时候,Connection对象自带一个Delete()方法能够起到这个功能,可是不幸的是这个方法是从基类继承过来的私有方法,因此我加了这个Remove方法来调用Delete方法。Delete方法从From Connector和To Connector以及GraphControl的Connections集合中移除该Connection

 

请继续阅读Netron开发快速上手(二):Netron序列化

相关文章
相关标签/搜索