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

相關文章
相關標籤/搜索