版權全部,引用請註明出處:<<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);