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

本文所用示例下载FlowChart.zip

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

Netron 2.2原版代码下载

一、      概述

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

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

  • GraphControl:代表的是画布对象,所有的图形对象都是在画布上展现,同时画布对象管理着图形对象的各种行为,如拖动,变形,选中以及连接等。也可以通过画布对象访问到所有的图形对象。
  • Shape:代表的是一个图形对象
  • Connector:代表的是图形对象上的连接点,两个连接点之间可以产生一条连接线。连接点对象只能依附图形对象而存在。
  • Connection:代表的是两个连接点之间的连接线

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

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

二、      GraphControl控件

将NetronGraphLib添加到解决方案

打开一个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来得到的。

三、      开发图形:Shape

我们打算画一个矩形图形,如下图

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

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

2、  改写ShapeInitEntity方法

我们需要在该方法中初始化对象的矩形坐标,以及设定画边框的画笔Pen和Shape的背景颜色ShapeColor,

protected override void InitEntity()
{
base.InitEntity();
Pen = new Pen(Color.FromArgb(, , ));
ShapeColor = Color.FromArgb(, , );
Rectangle = new RectangleF(, , , );
}

不要忘了先调用基类的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对象自动管理的。

四、      增加图形对象的功能

1、  让矩形图形显示文本

在InitEntity()方法中添加下面代码

protected override void InitEntity()
{
……
Text = "顺序图形";
Font = new Font("宋体", );
……
}

在Paint()方法中添加画出文本的代码

public override void Paint(Graphics g)
{
……
if (!string.IsNullOrEmpty(Text))
g.DrawString(Text,this.Font, this.TextBrush,System.Drawing.RectangleF.Inflate(Rectangle,,-));
}

下面是效果图

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 == && 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

3、  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():用来返回图形对象的右键菜单

五、      连接图形:ConnectorConnection

有了Shape图形对象后,我们要在图形对象之间画连接线。

1、  添加连接点

首先,我们要给图形对象添加连接点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有多个连接

2、  重写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 / );
} if (c == m_rightConnector)
{
return new PointF(Rectangle.Right, Rectangle.Top + Rectangle.Height / );
} return new PointF(, );
}

这时我们再运行程序,把鼠标移到Shape上,就可以看到Shape已经有了左右两个连接点;当鼠标移动到连接点上方时,鼠标会变成一个小的绿色方块,表示现在可以通过按下鼠标并拖动来画一条连接线Connection。这些功能都是通过GraphControl的OnMouseDown, OnMouseMove, OnMouseUp里的代码自动实现的。我们在后续篇章中还要讨论GraphControl里关于处理鼠标事件的代码。

             

3、  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的坐标点。

六、      自定义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类

1、  实现自定义连接线

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

                

我们先定义自己的连接线类

    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[];
points[] = From.Location;
points[] = new PointF(From.Location.X, To.Location.Y);
return points;
} points = new PointF[]; t_from = From.Location;
t_to = (To != null) ? To.Location : ToPoint; points[] = t_from;
points[] = new PointF(t_to.X, t_from.Y);
points[] = 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对象

2、  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序列化

Netron开发快速上手(一):GraphControl,Shape,Connector和Connection的更多相关文章

  1. Netron开发快速上手(二):Netron序列化

    Netron是一个C#开源图形库,可以帮助开发人员开发出类似Visio的作图软件.本文继前文”Netron开发快速上手(一)“讨论如何利用Netron里的序列化功能快速保存自己开发的图形对象. 一个用 ...

  2. Java开发快速上手

    Java开发快速上手 前言 1.我的大学 2.对初学者的建议 3.大牛的三大特点 4.与他人的差距 第一章 了解Java开发语言 前言 基础常识 1.1 什么是Java 1.1.1 跨平台性 1.2 ...

  3. php扩展开发-快速上手

    系统环境CentOS release 6.5 (Final) PHP版本php-5.6.27 扩展开发需要有php环境及php的源代码,我的PHP安装目录/home/zhangxiaomin/stud ...

  4. PhalApi 2.7 开发快速上手

    PhalApi是一款国人制作的PHP纯后端框架.它的开发相当简单,同时也具备文档生成等特色功能.下面,我通过简单的几点,让你可以快速入门使用该框架的开发. 建议使用PHPStorm作为IDE,代码提示 ...

  5. [译]:Xamarin.Android开发入门——Hello,Android快速上手

    返回索引目录 原文链接:Hello, Android_Quickstart. 译文链接:Xamarin.Android开发入门--Hello,Android快速上手 本部分介绍利用Xamarin开发A ...

  6. 微信小程序开发平台新功能「云开发」快速上手体验

    微信小程序开发平台刚刚开放了一个全新的功能:云开发. 简单地说就是将开发人员搭建微信小程序后端的成本再次降低,此文刚好在此产品公测时,来快速上手看看都有哪些方便开发者的功能更新. 微信小程序一直保持一 ...

  7. [Full-stack] 快速上手开发 - React

    故事背景 [1] 博客笔记结合<React快速上手开发>再次系统地.全面地走一遍. [2] React JS Tutorials:包含了JS --> React --> Red ...

  8. 如何比较Keras, TensorLayer, TFLearn ?——如果只是想玩玩深度学习,想快速上手 -- Keras 如果工作中需要解决内部问题,想快速见效果 -- TFLearn 或者 Tensorlayer 如果正式发布的产品和业务,自己设计网络模型,需要持续开发和维护 -- Tensorlayer

    转自:https://www.zhihu.com/question/50030898/answer/235137938 如何比较Keras, TensorLayer, TFLearn ? 这三个库主要 ...

  9. 前端开发工具包 WijmoJS 2019V1正式发布:全新的在线 Demo 系统,助您快速上手,开发无忧

    ​ 前端开发工具包WijmoJS在2019年的第一个主要版本2019V1已经发布,本次发布包括了更加易用的在线Demo系统.各控件新增功能.NPM 包的改动,以及全新的浏览器API组件. WijmoJ ...

随机推荐

  1. C#中JSON序列化和反序列化

    有一段时间没有到博客园写技术博客了,不过每天逛逛博客园中大牛的博客还是有的,学无止境…… 最近在写些调用他人接口的程序,用到了大量的JSON.XML序列化和反序列化,今天就来总结下json的序列化和反 ...

  2. CSS制作凹环特效

    就是在地面上打凿出凹的圆环效果,利用linear-gradient线性渐变增强内环质感,再用伪类after元素设置中心圆凸块的位置以及大小与跟内环之间的阴影度,然后设置内环的颜色就行了:第四个环上面的 ...

  3. HTML 定时页面跳转

    有 2 种方法可以实现 html 的定时页面跳转,1.meta refresh 实现.2.JavaScript 实现. 1.通过 meta refresh 实现 3 秒后自动跳转到 http://ww ...

  4. mvc中form表单提交的几种形式

    第一种方式:submit 按钮 提交 <form action="MyDemand" method="post"> <span>关键字: ...

  5. Android开发1:基本UI界面设计——布局和组件

    前言 啦啦啦~本学期要开始学习Android开发啦~ 博主在开始学习前是完完全全的小白,只有在平时完成老师要求的实验的过程中一步一步学习~从此篇博文起,博主将开始发布Android开发有关的博文,希望 ...

  6. CoreData

    之前在学习使用SQLite时, 需要编写大量的sql语句,完成数据的增删改查,但对于不熟悉sql语句的开发人员来说,难度较大,调试程序比较困难. 由此出现CoreData框架,将sql的操作转换成为对 ...

  7. UIWebView保存网页中的图片(转载)

    现在H5混合原生开发的方式越来越流行,也就要用到UIWebView控件.在开发过程中,我们可能会遇到一个需求,要求我们保存网页上的图片,当用户点击图片的时候,就可以让用户选择是否下载图片. 在系统自带 ...

  8. (二)Maven的安装与环境配置

    想要安装 Apache Maven在Windows 系统上, 需要下载 Maven 的 zip 文件,并将其解压到你想安装的目录,并配置 Windows 环境变量. 所需工具 : 1.JDK 2.Ma ...

  9. Spring实现AOP的4种方式

    了解AOP的相关术语:1.通知(Advice):通知定义了切面是什么以及何时使用.描述了切面要完成的工作和何时需要执行这个工作.2.连接点(Joinpoint):程序能够应用通知的一个“时机”,这些“ ...

  10. [转载]Linux 线程实现机制分析

    本文转自http://www.ibm.com/developerworks/cn/linux/kernel/l-thread/ 支持原创.尊重原创,分享知识! 自从多线程编程的概念出现在 Linux ...