(原創)[C#] GDI+ 之鼠標交互:原理、示例、一步步深入、性能優化
一、前言
“GDI+”與“鼠標交互”,乍一聽好像不可能,也無從下手,但是實現原理比想象中要簡單很多。
基于“GDI+”的“交互”,應用場景也很多,比如:流程圖、數據圖表、思維導圖等等。
本篇文章就通過多個示例來講解一下 GDI+ 與鼠標交互的原理,以及如何去實現。
每一個示例實現后,都會對示例進行優化,主要是解決一些在實際應用中比較常見的問題,比如:閃爍、資源占用高等等。
而在最后,會基于實際的應用場景——在背景圖上繪制圖形并進行鼠標交互——編寫一個示例。
接著會使用實際應用場景內必備的、也是核心的“局部刷新”技術對示例進行優化。
相信看完的你,一定會有所收獲!
本文地址:http://www.rzrgm.cn/lesliexin/p/16554752.html
二、基本原理
GDI+ 與鼠標交互的原理非常簡單:判斷鼠標是否在 GID+ 圖形上,然后根據鼠標的不同狀態,執行不同的效果。
估計很多人看到這句話就直接恍然大悟了。確實,原理就是這么簡單。
下面,我們首先來簡單實現一個簡單的交互效果:可以用鼠標拖動的矩形。
三、示例1:可以用鼠標拖動的矩形
(一)設計器界面
程序界面如下:

我們的繪制及交互區域就是 panel1,所以為 panel1 綁定以下幾個鼠標相關的事件:

(二)代碼實現
1,添加全局變量
為了與鼠標交互,我們需要以下兩個全局變量:

其中,rectShape 是我們所繪制矩形的位置和尺寸;pointLast 是上次鼠標的位置。
2,繪制矩形方法
繪制矩形很簡單,直接在背景上畫一個矩形即可。
GDI+ 中繪制矩形的方法如下:
(下圖來自MSDN)

不過為了防止殘留,我們在畫矩形前需要先清空一下背景。
(下圖來自MSDN)

原理示意如下:

對應的代碼如下:

3,鼠標交互操作實現
(1)當鼠標在 panel1 中點擊時,我們要判斷鼠標點擊的位置是否處于我們繪制的矩形內。
如果是,則記錄當前鼠標的位置;
如果不是,則清空記錄的鼠標位置;

(2)當按著鼠標按鍵并拖動鼠標時,我們要判斷是否有記錄過之前鼠標的位置。
如果滿足條件,就證明是現在鼠標是按著所繪制的矩形進行拖動了。
所以,我們要計算一下這次鼠標的位移量,并計算矩形的新位置,然后重新在新位置繪制矩形。
這一步,就是交互效果的核心。在拖動的過程中,我們會根據鼠標的位置不斷的計算并重新繪制新的矩形。在視覺效果上,就是我們拖動著矩形在動。

因為不斷在重新繪制矩形,所以這里是最能體現 GDI+ 性能的地方,不同的寫法,性能相差很大,這也是后續所要優化的地方。
(3)當松開鼠標按鍵時,將記錄的鼠標位置清空。
上面的 MouseMove 事件會因為不滿足條件,而結束重繪。

(三)效果演示
編譯運行程序,我們會發現已經可以使用鼠標拖動矩形了。

我們會發現,拖動矩形時會出現閃爍的情況。而且窗口越大,閃爍越明顯。
這是因為我們是先清空背景、然后再繪制矩形,這個清空再繪制的過程,就會閃爍。
下面,我們就來優化一下,解決閃爍的問題。
(四)“閃爍”問題優化
解決“閃爍”,我們最先想到的就是開啟“雙緩沖”,不過在這里,開啟“雙緩沖”效果不大,因為閃爍的原因在于我們自己不斷的清空再繪制。
所以,我們優化的核心就是不再清空背景。
開啟雙緩沖的方式如下:

我們會發現,在兩次拖動變化之間,可以看作是先將原矩形填充為背景色,再在新位置繪制一個新的矩形。
示意圖如下:

我們按照示意圖編寫代碼如下:

(五)優化后效果演示
編譯運行程序,我們再次拖動矩形,會發現不再有閃爍的情況。

四、示例2:可以用鼠標拖動的圓形
在實現了可以被鼠標拖動的矩形后,我們再來實現可以被鼠標拖動的圓形。
因為圓形和矩形是不一樣的:圓形既有可見區域,也有不可見區域。
如圖所示:

我們本節就看一下在實現上都有哪些不同。
(一)設計器界面
設計器界面同上,增加一個按鈕用來添加圓形。

(二)代碼實現
1,添加全局變量
因為 GDI+ 中繪制圓形的參數和矩形是一樣的,都是一個 Rectangle ,所以我們可以復用之前的全局變量,不用進行修改。
(下圖來自MSDN)

2,繪制圓形方法
這里,我們直接采用上節優化后的方法去實現,即:將舊矩形填充背景色,再在新位置繪制新圓形。
原理示意見上節,具體代碼如下:

3,鼠標交互操作實現
這里與上節繪制矩形的原理一樣,只需要在 MouseMove 事件中將繪制矩形的方法改為繪制圓形的方法即可。
代碼修改如下:

(三)效果演示
編譯運行,可以發現我們可以正常使用鼠標拖動繪制的圓形。
【注:我們會發現,同樣是優化后的方法,在繪制“矩形”時不會閃爍,但是在繪制“圓形”時會閃爍,這是因為繪制圓形會更加消耗性能,關于如何解決閃爍的問題,參見下面:“六、使用“局部刷新”技術對【示例3】進行優化”。因為本節內容的重點不在于此,所以未在此節解決閃爍問題。】

在拖動的時候,我們會發現一個問題:就是我們的鼠標即不在圓形上,而是在圓的四個邊角處,也能正常拖動圓形。
如下:

這是因為圓形和矩形不一樣,圓形是有可見區域(即顯示的圓形)和不可見區域(即非圓形區域),雖然不可見,但仍然是存在的,所以仍然會正常捕獲到鼠標的點擊。
這里,我們在繪制圓形時將真正的范圍填充上顏色,效果會很明顯。

下面,我們就針對這個鼠標捕獲區域的問題進行優化。
(四)鼠標捕獲區域優化
首先,最關鍵的地方就是在鼠標點擊的時候,也就是 MouseDown 事件。

我們判斷鼠標是否落在圓形內,不能再通過當前的方法。因為這個只能判斷矩形。我們要判斷鼠標是否在圓形內,通過通過 Region 去判斷。
(下圖來自MSDN)

首先,我們添加一條和圓形同尺寸的圓形路徑,然后基于此路徑創建 Region ,接著判斷鼠標是否在此 Region 內。
具體的代碼如下:

(五)優化后效果演示
我們再次編譯運行程序,會發現只能我們的鼠標點擊在圓形內,才能正常拖動圓形。
為了更明顯的演示,我們為非圓形區域填充上顏色,再次操作如下:

五、示例3:可以用鼠標拖動的圓形,但背景圖不受影響
上面的示例看下來,似乎已經沒有問題了。但是在實際應用過程中,卻有一個不可忽視的元素:背景圖(此處的背景圖是廣義上的背景圖,可指圖片、其它GDI+ 圖形等等,但原理都是一樣的)。
因為前面的示例背景都是純色,所以我們看不出來,現在我們為 panel1 加上背景圖,再次運行程序,我們看下效果:

可以看到,拖動過的地方背景直接被擦了。這還是優化后的代碼,如果是最開始的“先清除背景再繪制圖形”,則在第一次拖動的時候,整個背景圖就都沒了。
本節,我們就來看一下:如何在用鼠標拖動圓形時,背景圖還正常顯示不受影響。
(一)設計器界面
設計器界面同上,不作變化。
(二)代碼實現
1,生成背景圖
首先,我們寫一個方法,生成一張背景圖,當然也可以使用現成的圖片。
然后將這張背景圖保存為全局變量,以供后續使用。

2,修改繪制圓形方法
既然背景圖受到影響,我們想到的最直接方法便是在每次繪制圓形時,都重新將背景圖繪制一遍。
不過將整個背景圖完整的重繪一遍會太過消耗資源,所以我們可以采取之前的優化思路,就是填充原矩形、繪制背后矩形,不過這里的填充不再是背景色,而是背景圖。
首先,我們需要計算一下原矩形在背景圖中對應的位置和尺寸,然后將這塊背景繪制上去,接著再繪制新的矩形。
我們使用這個重載方法進行背景圖的繪制:
(下圖來自MSDN)

具體的代碼如下:

(三)效果演示
編譯運行,可以發現背景確實不受影響了。

不過上節中出現的在繪制圓形時閃爍的問題也更嚴重了。
那么下面,我們就從根本上來解決一下閃爍的問題。
六、使用“局部刷新”技術對【示例3】進行優化
在前面的示例中,使用同樣的優化方式,在繪制矩形時不閃爍,而在繪制圓形時卻會閃爍,雖說是因為繪制圓形更耗性能,但也說明了前面的優化還遠遠不足。
而問題的根源,就在于刷新的面積太大了。所以我們的優化方向,就在于怎么將這個“刷新面積”減小,也就是所謂的“局部刷新”技術。
下面,我們就以【示例3】為例來演示下如何使用“局部刷新”技術。
(一)“剪輯區域”
與“局部刷新”所對應的,就是“剪輯區域”,顧名思義,就是專門剪輯出來用來重繪的區域。
在計算“剪輯區域”時,為了方便計算和演示,我們直接將拖動時剛好包含“原矩形”和“新矩形”的矩形區域當成“剪輯區域”。

(二)修改繪制圓形方法
在繪制圓形時,我們首先要計算剪輯區域,然后獲取剪輯區域所對應的背景圖,接著設置剪輯區域,并繪制新矩形。

(三)效果演示
編譯運行程序,可以看到在拖動圓形時,不會再出現閃爍的問題,同時各種資源的占用也很低。

七、“局部刷新”技術在實際場景中的應用
在實際應用場景中,并不是簡單的一個背景一個圖形。在需要用到 GDI+ 交互的場景,往往都會在同一個區域內有好多個不同的 GDI+ 圖形。
這種場景的基本繪制流程一般如下:
1,將諸多 GDI+ 圖形保存到一個集合內,一般是以類的形式,類里面包含圖形類型、繪制此圖形所需要的參數、附加參數等。
2,在繪制時,將背景圖(如果有的話)和圖形集合繪制到一個臨時Bitmap 上,然后將此臨時Bitmap 繪制到窗口上。
3,釋放臨時Bitmap等資源。
在這種流程下,如果按照“非局部刷新”的方式,就不免會出現閃爍、CPU內存占用高等問題。
所以,這種時候就必然要用到“局部刷新”技術。我們不用再將全部的圖形集合和背景圖繪制到一張臨時Bitmap上,而是先計算剪輯區域,然后判斷圖形集合內有哪些圖形在剪輯區域內,之后僅重新繪制這些圖形即可。
八、源代碼下載
本文演示的程序源代碼如下:
https://files.cnblogs.com/files/lesliexin/GdiInteractive.7z
九、總結
在這個新技術層出不窮的時代,GDI+ 已經被冠上諸如“上個時代的技術、落后的技術、性能很差的技術”等等名詞。
但是 GDI+ 的效率并不低下,只是很少有能夠發揮出 GDI+ 的正常性能,更別說觸摸到 GDI+ 的極限了。
當然,本人的水平也有限,只能說勉強夠用而已。
新技術,給了我們更多的選擇,不過技術是沒有先進落后之分的,只有合適與不合適之別。
所以請對自己掌握的技術多一些信心,多一些耐心。
在此,作者與諸君共勉!
本人水平有限,文章難免有所疏漏,歡迎大家評論指正。
-【END】-

浙公網安備 33010602011771號