[原創(chuàng)]《C#高級(jí)GDI+實(shí)戰(zhàn):從零開發(fā)一個(gè)流程圖》第09章:增加貝塞爾曲線,上、下、左、右連接點(diǎn)
一、前言
前面的課程我們添加了諸多形狀,但連線還只有直線這一種樣式,而且也只能連接形狀的中心點(diǎn)。我們本節(jié)課就來增加一種很常見的連線樣式:貝塞爾曲線。同時(shí)也對形狀增加多個(gè)不同的連接點(diǎn),不再只連中心了。
相信看完的你,一定會(huì)有所收獲!
本文地址:http://www.rzrgm.cn/lesliexin/p/19033113
二、先看效果
通過視頻我們可以看到,我們本節(jié)課主要實(shí)現(xiàn)3個(gè)效果:
1,支持添加貝塞爾曲線
2,所有形狀都支持上、下、左、右四個(gè)連接點(diǎn)
3,對貝塞爾曲線的控制點(diǎn)進(jìn)行了優(yōu)化,效果更好
下面我們就分別來講解一下。
隨便說一下,從本節(jié)開始我們的代碼就和Gitee上同步了,讀者可自行拉取編譯。
Gitee地址:https://gitee.com/lesliexin/flowchartdemo
三、效果1:添加貝塞爾曲線
(一)貝塞爾曲線類
首先我們來看一下MSDN上對于貝塞爾曲線的說明:

可以看到共需要4個(gè)點(diǎn):起點(diǎn)、控制點(diǎn)1、控制點(diǎn)2、終點(diǎn)。
我們的連線定義里有開始形狀和結(jié)束形狀,可獲取到起點(diǎn)和終點(diǎn),那么還需要額外定義兩個(gè)屬性——控制點(diǎn)1和控制點(diǎn)2——嗎?
答案是:不需要。我們的目的不是做成是PS中的鋼筆那樣可以可以自由調(diào)整控制點(diǎn)來改變連線的樣式,我們的目的是提供一個(gè)統(tǒng)一規(guī)則樣式的連線,所以我們只需要按照一定的規(guī)則根據(jù)起點(diǎn)和終點(diǎn)計(jì)算出這兩個(gè)控制點(diǎn)即可。
我們先定義一個(gè)簡單的規(guī)則:控制點(diǎn)1:起點(diǎn)右側(cè)50像素;控制點(diǎn)2:終點(diǎn)左側(cè)50像素。
規(guī)則很簡單,也不太完善,我們先照此實(shí)現(xiàn),下文會(huì)進(jìn)行優(yōu)化。
下面我們就基于連線基類派生出一個(gè)貝塞爾曲線類:

可以看到,實(shí)現(xiàn)起來還是很簡單的。
(二)畫布增加對貝塞爾曲線的支持
我們現(xiàn)在的一切都是在畫布(FCCanvas)中實(shí)現(xiàn),所以我們來看一下如何在畫布中添加對貝塞爾曲線的支持。
我們先來看一下“直線連線”是在畫布的哪里使用的:

可以看到是在OnMouseDown事件中判斷選擇了兩個(gè)形狀后便添加了一條新的直線連線,那么我們就要控制一下,可以根據(jù)需要添加直線還是貝塞爾曲線。
我們定義一個(gè)屬性,來控制添加連線時(shí)添加的是什么類型連線:

這里我們定義為string類型,一是方便后續(xù)擴(kuò)展,如果定義為bool、int、甚至是enum,擴(kuò)展性都不太好,因?yàn)楹罄m(xù)的連線可能是第三方自定義的,非string類型要么閱讀性不好要么擴(kuò)展性不好;二是方便后續(xù)動(dòng)態(tài)反射,這個(gè)對于后續(xù)開放用戶自定義形狀是必要的。
然后,我們在OnMouseDown事件中,根據(jù)此屬性來判斷添加哪種類型:

(三),應(yīng)用畫布
首先,我們添加一個(gè)下拉框控件,內(nèi)容為直線和貝塞爾曲線:

在下拉框的改變選中項(xiàng)事件中,我們根據(jù)選擇的項(xiàng)不同,設(shè)置畫布的連線類型:

到此,我們就實(shí)現(xiàn)了貝塞爾曲線的支持,是不是感覺意外的簡單?這就是抽象的好處。
注:從本節(jié)開始,課程代碼就提交到了GITEE上了,大家可自行拉取編譯。
四、效果2:上、下、左、右連接點(diǎn)
我們之前的課程都是直接連接的形狀的中心點(diǎn),效果是一言難盡,主要是方便講解用。隨著我們的課程步入正軌,這點(diǎn)也需要同步解決,我們本節(jié)就來擴(kuò)展一下,支持上、下、左、右四個(gè)連接點(diǎn)。
注:近期階段支持上、下、左、右四個(gè)連接點(diǎn)已經(jīng)足夠用了,當(dāng)然后期我們會(huì)開放此限制,完全的由派生類自行定義有多少個(gè)連接點(diǎn)、各連接點(diǎn)的坐標(biāo)又是哪里。
(一)形狀基類擴(kuò)展
因?yàn)檫@是所有形狀的改動(dòng),所以我們要對形狀基類進(jìn)行擴(kuò)展。
注:為了講解課程需要,是重新下定義了個(gè)基類: ShapeBaseV2,大家自行實(shí)現(xiàn)時(shí)直接修改原類即可。下同,不再贅述。
1,連接點(diǎn)枚舉

我們定義了四個(gè)連接點(diǎn),從左開始順時(shí)針依次是:左->上->右->下。
2,獲取連接點(diǎn)方法
我們同樣定義一個(gè)帶默認(rèn)實(shí)現(xiàn)的虛方法。因?yàn)槲覀儸F(xiàn)在的形狀都是基于一個(gè)“矩形”來繪制的,所以默認(rèn)實(shí)現(xiàn)就是矩形的四個(gè)邊的中點(diǎn)坐標(biāo):

我們定義為虛方法,是方便派生形狀自行決定連接點(diǎn)的坐標(biāo)是哪里,像平行四邊形,左、右兩個(gè)連接點(diǎn)就是進(jìn)行偏移才能在斜邊上。
3,移除獲取中心點(diǎn)方法
因?yàn)楝F(xiàn)在已經(jīng)有連線點(diǎn)了,所以不再需要獲取中心點(diǎn)的方法,我們直接移除即可。

(二)形狀派生類修改
形狀基類已經(jīng)修改,所以我們的派生類也需要同步修改。
1,平行四邊形
因?yàn)槠渌螤畹乃膫€(gè)邊都是與矩形區(qū)域重合的,所以不需要修改。



哪怕是菱形,其四個(gè)頂點(diǎn)正好是矩形區(qū)域的四個(gè)邊的中心點(diǎn),所以也不需要修改。

只有平行四邊形需要重寫獲取連接點(diǎn)坐標(biāo)方法,因?yàn)樽蟆⒂覂蓚€(gè)連接點(diǎn)要進(jìn)行偏移才能在斜邊上:


(三)連線基類修改
上面的形狀已經(jīng)支持了不同的連接點(diǎn),那么連線也要做同步的修改,主要是要記錄開始形狀的連接點(diǎn)、和結(jié)束形狀的連接點(diǎn)。

(四)連線派生修改
我們對連線的修改是獲取開始點(diǎn)和結(jié)束點(diǎn)坐標(biāo)的地方,我們之前是調(diào)用形狀的獲取中心點(diǎn)方法,而現(xiàn)在要改成根據(jù)連接點(diǎn)獲取對應(yīng)連接點(diǎn)坐標(biāo)。
1,直線

2,貝塞爾曲線

(五)畫布增加連接點(diǎn)支持
當(dāng)形狀和連線都修改好后,我們的畫布也要同步添加對連接點(diǎn)的支持。
因?yàn)檫@次是讓連線支持連接上、下、左、右不同的連接點(diǎn),所以我們的核心便是記錄下連線開始和結(jié)束形狀的連接點(diǎn)信息然后在生成連接時(shí)將連接點(diǎn)信息賦于連線。
所以我們先定義兩個(gè)屬性:

然后在OnMouseDown中,添加連線時(shí)將這兩個(gè)屬性的值賦給連線:

(六)應(yīng)用
我們下面就來編寫程序來看下不同連接點(diǎn)的效果:
1,設(shè)計(jì)器界面
我們增加兩個(gè)下拉框,來分別設(shè)置開始和結(jié)束的連接點(diǎn)是上、下、左、右中的哪個(gè):

注:這種方式看文首的演示視頻就會(huì)發(fā)現(xiàn)用起來很繁瑣,不直觀。我們要一點(diǎn)點(diǎn)深入,在后面我們就會(huì)對其改造成鼠標(biāo)點(diǎn)擊開始形狀的某一連接點(diǎn),然后拖動(dòng)著去結(jié)束形狀的某一連接點(diǎn),松開鼠標(biāo)完成連線操作,而且在拖動(dòng)時(shí)會(huì)實(shí)時(shí)顯示連接的半透明虛線效果。
2,切換連接點(diǎn)
我們在下拉框的選擇改變事件中,對畫布的連接點(diǎn)屬性進(jìn)行修改:
開始形狀的連接點(diǎn):

結(jié)束形狀的連接點(diǎn):

就像上一小節(jié)一樣,很簡單的改動(dòng),就支持了不同的連接線,大家可自動(dòng)拉取代碼編譯嘗試。
五、實(shí)現(xiàn)效果3:貝塞爾曲線控制點(diǎn)優(yōu)化
我們實(shí)現(xiàn)了上下左右連接點(diǎn)后會(huì)發(fā)現(xiàn)(看文首的演示視頻可以明白):貝塞爾曲線效果只有在某些情況下效果很好,其它時(shí)候曲線的效果都很別扭。這是因?yàn)槲覀冊诘?小節(jié)定義貝塞爾曲線時(shí),兩個(gè)控制點(diǎn)是寫死的,而現(xiàn)在我們支持了上下左右不同的連接點(diǎn),所以我們的貝塞爾曲線也得根據(jù)連接點(diǎn)的不同而同步調(diào)整控制點(diǎn)的位置。
我們先看下原本控制點(diǎn)的計(jì)算方式:

這種情況只適合:開始形狀在結(jié)束形狀的左側(cè)、開始形狀的連接點(diǎn)是‘右’、結(jié)束形狀的連接點(diǎn)是‘左’。所以我們依此規(guī)律,根據(jù)連接點(diǎn)的不同而計(jì)算不同的坐標(biāo):

當(dāng)然,計(jì)算控制點(diǎn)的方法有很多,不同的控制點(diǎn)曲線的效果也不相同,我們這里采取的是比較簡單的方式,效果不錯(cuò)。
我們只需要修改貝塞爾曲線的定義即可,其它的畫布、程序都不需要改動(dòng),就可以看到效果了。
隨課代碼為了每節(jié)課不干擾,所以需要重新修改畫布、程序等等。
六、結(jié)語
我們本節(jié)課實(shí)現(xiàn)了一個(gè)新的連線樣式:貝塞爾曲線,同時(shí)還支持了上、下、左、右四個(gè)連接點(diǎn),到此終于有點(diǎn)正式流程圖的樣子了。
我們看本節(jié)的代碼就會(huì)感受到,添加新功能意外的改動(dòng)很少,這就是抽象的魅力。
其實(shí)到本節(jié)課為止,基礎(chǔ)的東西就講完了,我們后面的課程就是要在各個(gè)細(xì)節(jié)處下功夫了,向著正規(guī)的流程圖去前進(jìn)。后面的課程,實(shí)現(xiàn)思路和邏輯大于技術(shù)代碼,更多的是如何去實(shí)現(xiàn)、去更好的實(shí)現(xiàn)想要的效果,而這些,對于任何框架和語言都有用。
下節(jié)課,我們就來解決本節(jié)課選擇連接點(diǎn)繁瑣的問題,我們來實(shí)現(xiàn)鼠標(biāo)拖動(dòng)到連接點(diǎn)來添加連接。敬請期待。
感謝大家的觀看,本人水平有限,文章不足之處歡迎大家評(píng)論指正。
-[END]-

浙公網(wǎng)安備 33010602011771號(hào)