技術沒有先進與落後,只有合適與不合適。html
本篇的自定義控件是:滾動條(ScollBar)。框架
咱們能夠在網上看到不少自定義的滾動條控件,它們大都是使用UserControl去作,即至少使用一個Panel或其它控件做滑塊,使用UserControl自己或另外一個控件做爲背景條,而有的複雜的還會加上頂端和底端的按鈕。這樣做的好處有不少,最主要的是支持承載更加複雜的視覺和動做效果,好比使用一系列圖片來實現很是炫麗的動畫效果等。ide
不過本次所實現的滾動條並不須要太多複雜的效果,扁平化樣式便可,因此就不須要使用UserControl,因此直接參照上一篇的LTrackBar,實現一個相似樣式的滾動條,這也是在上一篇以後緊接本篇的緣由之一。函數
就像實現上篇的LTrackBar時的圓角的座標計算是個難點,而後着重結合示意圖去講解同樣,在本篇,關於滾動條中比較反直覺的、不太好直觀想像的,我仍會結合示意圖去詳細講解。優化
相信看完的你,必定會有所收穫。動畫
本文地址:http://www.javashuo.com/article/p-hjkpknom-kw.htmlspa
滾動條(ScrollBar)控件是一個經常被忽略,可是應用卻極其頻繁的控件。因爲不少控件都自帶滾動條,因此每每使人忽略滾動條的存在。設計
可是這些控件自帶的滾動條有個很是大的缺點,那就是幾乎不能夠單獨對滾動條進行重繪,甚至連調整控件自帶滾動條的寬度這種」看起來很簡單「的操做,真正實現起來的工做量簡直使人」懷疑人生「。orm
並且,系統的滾動條控件:VScrollBar和HScrollBar,其樣式還是最基本的那樣,其可重繪性很是小。xml
系統控件自帶的滾動條,在通常的WinForm程序中,看着還挺不錯的,可是一旦你的WinForm程序中使用了大比例的自定義控件後,系統的滾動條就有點」刺眼「了。因此,便須要本身的滾動條控件。
參考上一篇的 LTrackBar控件,我想實現的滾動條的外觀樣式以下:
(1)支持鼠標拖動。
(2)支持點擊非」滑塊「區,改變滑塊位置。
(3)支持鼠標進入時改變滑塊顏色。
(4)支持修改各部分顏色。
(1)支持改變滾動條方向:垂直或橫向。
(2)支持改變顏色。
(3)支持圓角、直角顯示。
滾動條控件LScrollBar與LTrackBar相似,一樣是分爲背景條(Bar)和滑塊(Slider)。所使用的核心技術還是GDI+。
在這裏,我會對滾動條進行詳細的講解,包括滾動條的效果、比例的計算等。
在本節的講解中,我將以」直角「、」垂直「樣式的滾動條進行講解說明。
以下圖所示,咱們將」滾動條「和」顯示內容「拆分紅兩部分。
其中,半透明的黑色部分是」屏幕「,也就是咱們實際能夠看到的範圍。而「文檔」的實際長度要遠遠超過屏幕可顯示部分,因此就須要經過「滾動條」來上下「滾動」來看文檔的其餘部分。
那麼,在直觀的想像中,」滾動「應該以下面的動圖演示那樣:
可是實現狀況卻不是如此,由於如今中」屏幕「是不會動的,那麼」滾動「的只能是」文檔「,因此真實的」滾動「應該像下面這樣:
也就是說,咱們在將滾動條的滑塊」向下「拖動時,」文檔「實際是」向上「拖動的。
在具體使用程序實現時,也就是改變」文檔「的」Y「座標值。
在知道了滾動條的內部原理後,就須要一些比值的計算了,好比滾動條「滑塊」的長度、「滑塊」的拖動距離與「文檔」位置改變的距離的比值等。
首先,以下圖所未,我加上了一些標識,以方便描述。
其中:
(1)
文檔的長度=A。
屏幕的長度=可見文檔部分的長度=B。
那麼,不可見的文檔長度=A-B=C。
(2)
滾動條的長度=D。
滑塊的長度=E。
滑塊可拖動的範圍=D-E=F。
(3)
在實現的時候,咱們已經的量有:A(文檔長度)、B(屏幕長度)、D(滾動條長度),而E(滑塊長度)是不知道的,是計算出來的。
在平常使用過程當中咱們也會發現,若是要顯示的內容越多,滑塊的長度越短。
其中的比例關係以下:
B/A=E/D
可得:E=D*B/A。
此時咱們已經獲得了滑塊的長度,這是一個很是重要的基本量。
獲得E(滑塊長度)後,F(滑塊可移動範圍)也能夠獲得:
F=D-E。
(4)
由於「屏幕」已經顯示了一部分的「文檔」,因此真正須要經過滾動條去查看的「文檔」部分是C(不可見文檔長度):
C=A-B。
由此,咱們能夠獲得一個重要的比值關係:
n*F(滑塊拖動範圍)=m*C(不可見文檔長度)
(注:加上n、m是由於F與C並非1:1的關係)
n/m=C/F
這個比值咱們使用X代替,即:
X=C/F=(A-B)/(D-E)。
(5)
爲何要計算這個比值,由於咱們在使用滾動條時,並非去考慮鼠標是向下拖動仍是向上拖動,以及拖動了多少距離。而是隻須要知道滾動條的「滑塊」的位置,更準確的說是「滑塊」頂端距滾動條頂端的距離,以下圖所示,咱們所須要知道的只是下圖中F(注意:此F不是上面中的F)的值而已。
經過F的值,咱們計算出B的值,而後再使「文檔」向上移動B的距離,這就是滾動條的使用方法。
那麼,怎麼經過F來獲得B就很是重要了,這就須要一個比值,經過這個比值將F轉換爲B,這個比值就是上面的X。
經過X,咱們能夠獲得如下信息:
a,拖動滾動條時,文檔應該「上移」的距離B:
B=F*X。
b,文檔位置改變後,滾動條所處的位置F:
F=B/X。
因爲本篇實現的滾動條控件(LScrollBar)是參考前篇的LTrackBar來實現的,因此此處僅做提綱用,具體操做見前篇。
新建類:LScrollBar.cs
添加繼承:Control(須要添加引用:System.Windows.Forms.dll)
修改可訪問性爲:public
因爲本控件和前篇的LTrackBar有極大的類似性,因此一些屬性也能夠直接拿來使用。這也是「複用」的一種。
這是一種提示顏色,像一些軟件、網頁的滾動條在平時是一種顏色,在鼠標處於滾動條上方時又是一種顏色,本屬性就是實現的這種效果。
此處和LTrackBar不同,對於滾動條(LScrollBar)而言,只有兩種方向:水平、垂直。因此咱們須要新建一個方向枚舉,爲了不與LTrackBar方向枚舉衝突,咱們將枚舉命名爲:OrientationScrollBar
在改變滾動條的方向時,下面的代碼會自動交換滾動條的寬和高。
這裏的尺寸是指滾動條(LScrollBar)的寬度(在垂直方向時)或高度(在水平方向時)。
爲了支持此屬性,還須要設置控件使之只能修改高度(在垂直方向時)或寬度(在水平方向時),就須要重寫SetBoundsCore。
在重寫了SetBoundsCore後,在設計器界面咱們便只能調整控件的寬或高。
這裏使用「文檔」這個說法是源自MFC,本處仍沿用其說法。其指的是所要顯示的總長度。
這裏用「長度」是方便說明,否則就要在垂直狀態時說「高度」,水平狀態時說「寬度」,太過繁瑣。
在設置了「文檔」長度時,咱們調用了兩個方法:pInit()和pChangeSliderLocation()。
其中pInit()的做用是初始化各個比例、計算滑塊長等。pChangeSliderLocation()的做用是改變滑塊的位置。其代碼以下:
指示窗口可顯示的長度。
這裏滑塊的位置指的時滑塊的頂端相對於滾動條頂端的距離。
這裏我將其設置爲公共只讀,是由於這個屬性更多的是用來查看,若是直接修改還須要計算比例什麼的,而爲了計算比例,就須要額外提供一些額外的值以支持其計算,很麻煩,因此就不讓用戶去直接修改。若是想修改滑塊的位置,則使用下面的「顯示位置」屬性去設置。
這裏的顯示位置就是「文檔」頂端與顯示窗口的距離。
以垂直狀態爲例,就是其Y座標,由於默認狀況下,系統的座標方向是向右爲正、向下爲正。而其原點就是顯示窗口的左上角,因此「文檔」的Y座標值即是個負數,爲了方便計算和處理,咱們將以正數的方式進行處理。
在用戶設置了此屬性時,咱們會自動進行相應的計算,並改變滑塊的位置(即上面的「滑塊位置」屬性的值),這樣對用戶而言,只須要知道「文檔」的位置就好了,這樣當其設置了文檔的位置後,滑塊的位置會自動改變。
本屬性是公共只讀,以備某些狀況下使用。
本屬性是公共只讀,以備某些狀況下使用。
日常狀態下,滾動條是支持鼠標按在滑塊上拖動的,因此滑塊的長度就不能過短。固然若是使用滾動條時僅是做爲顯示用,而不須要操做,則能夠將滑塊的最小長度設置爲0。
本屬性是爲了在使用鼠標滾輪上下滾動時,和按上下左右鍵時,滑塊每次移動的距離。
這個距離是「文檔」角度下的距離,不是滑塊真實移動的距離,這樣設置的緣由是方便使用者按須要設置,好比其想實現一個ListBox,每次按鍵都是一行的高度,此時就能夠將本屬性設置爲那一行的高度值。
對於本滾動條(LScrollBar)而言,只須要一個事件,那就是滑塊發生了滾動時的事件。
固然,估計不少人在初次實現時,會想到有不少事件,像點擊了滑塊事件、點擊了滑塊上面空白部分事件、拖動事件等等。可是咱們要記得以前所說的滾動條的工做方式:只須要知道滑塊的位置便可。前面事件的結果都是滑塊的位置發生了改變,因此能夠歸到一個事件裏面。
OnPaint是實現效果的根本,不過本次的實現內容比LtrackBar要簡單不少,總的來講就是先畫一個背景條,再畫一個滑塊。
由於咱們要實現鼠標處於滾動條上方時滑塊變色,因此咱們要將滑塊的顏色設置爲屬性:「鼠標進入滾動條後滑塊顏色」的值,並使控件發生重繪。
同上,咱們在鼠標離開滾動條後,將滑塊的顏色設置爲屬性:「滑塊顏色」的值。
在這裏,咱們須要肯定下鼠標點擊處的位置:點在了滑塊上、點在了滑塊上方、點在了滑塊下方。
根據平常使用經驗,在點擊非滑塊上時,是有相應的效果的,通常而方點擊滑塊上方空白處則表明「上一頁」,就是減去屬性「頁面長度」的值。同理,點擊滑塊下方的空白處則表明「下一頁」,就是加上屬性「頁面長度」的值。
這裏實現的是當鼠標點在了滑塊上時,按住鼠標拖動。
在上面的OnMouseDown中,咱們額外計算了兩個值:fAbove、fBelow,其做用即是爲了在此時計算滑塊的位置。
具體以下圖演示:
咱們想實現滾動鼠標滾輪鍵(中鍵)時滑塊跟着上下滾動,便須要重寫本方法。
其中須要用到事件的一個屬性:Delta,其MSDN的說明以下:
在滾輪向下滾動時,Delta的值爲負數;滾輪向上滾動時,Delta的值爲正數。
根據上面MSDN的解釋,咱們只須要知道滾輪是向上滾動仍是向下滾動就好了。根據滾輪是向上滾動仍是向下滾動,將顯示位置的值加上或減去屬性:「滾動間隔距離」的值。
本方法中,主要是爲了實現按鼠標箭頭鍵時執行至關的操做。
在滾動條是垂直狀態時,按上箭頭鍵,滑塊向上滾動;按下箭頭鍵,滑塊向下滾動。
在滾動條是水平狀態時,按左箭頭鍵,滑塊向左滾動;按右箭頭鍵,滑塊向右滾動。
在按鍵時,是將顯示位置的值加上或減去屬性「滾動間隔距離」的值。
爲了不拖動滑塊時、改變滾動條尺寸時滾動條閃爍,故在其構造函數中加上對雙緩衝的支持。
爲了達到雙擊控件就自動實現僅有的一個事件:L_Scrolled,因此在類的最上方加上默認事件支持。
在前面的屬性中,有很多屬性我除了加了Category和Description——這兩個的含意我在LTrackBar那篇講過,分別是分類和描述,還有一個「Browsable(false)」,以下圖所示,其做用是不在設計界面的「屬性」窗口中顯示。
在一些不可設置的屬性或者不想讓用戶直接經過屬性窗口設置的屬性,能夠添加Browsable(false)以達到此目的。
固然,在代碼界面,仍是能夠看到該屬性提示的。
以下圖那樣當鼠標放上去時彈出相應的中文提示,寫代碼時的智能提示中顯示中文提示。
要想實現該效果,首先要在屬性或方法上輸入」///「,此時VS會自動補全,以後即可以添加想要的提示了。像上面的屬性都是這樣寫的。
不過只這樣寫的話在同一個解決方案中是能夠顯示中文提示的,若是單獨引用生成的dll就沒有提示了,這時須要生成對應的xml幫助文檔,這樣在引用該dll時,VS會自動加載對應的xml文件,也就會有對應的提示了。
生成xml方法:選擇控件類庫屬性,在」生成「標籤頁中,勾選「XML文件文件」,VS會自動填入生成路徑,若是想生成到其餘地方能夠自行修改。
不過在單獨引用dll時要保證dll和xml文件在同一目錄下。
在本節中,咱們不止要演示滾動條LScrollBar的各類效果、特色,最主要的是演示一下怎麼如何使用LScrollBar。
咱們會在一個panel中添加50個按鈕,而後經過操做滾動條還使這些按鈕上下移動。
首先,咱們新建一WinForm程序,在上面添加以下幾個控件,其控件名以下:
接着,咱們雙擊「加載列表」按鈕,在其方法中寫入如下代碼。代碼功能是往panel1中添加50個按鈕。
最後,咱們雙擊滾動條lScrollBar1,在其方法中寫入如下代碼。代碼功能一是顯示當前滑塊的位置,以及當前全部按鈕距初始位置的距離;二是改變全部按鈕的位置,以實現滾動效果。
以後編譯並運行程序,其運行效果以下:
LScrollBar實現到當前這種程度,對我目前而言,以及對大多數須要使用自定義滾動條的地方而言都是足夠了的。可是,並非沒有缺點或可優化的地方。
其中最主要的一點,就是沒法直接代替系統控件自帶的滾動條,好比替換ListBox自帶的滾動條,替換TextBox自帶的滾動條等等。
之因此沒法替換,是由於對ListBox、TextBox等自帶滾動條的控件而言,其控制滾動條的滾動和處理滾動條的滾動是經過Windows消息去處理的,而LScrollBar並無攔截和處理這些滾動條消息。
若是想使用LScrollBar替換ListBox、TextBox等控件自帶的滾動條,能夠參考下面的思路:
首先要在LScrollBar中增長對滾動條消息的處理,包括攔截和發送。
而後,能夠直接將LScrollBar的寬度調成和ListBox、TextBox自帶滾動條一樣的寬度,而後覆蓋上;
或者將ListBox、TextBox自帶滾動條隱藏掉,在旁邊放上LScrollBar。
經過上面的方法,應該就能夠達到」自定義ListBox、TextBox控件的滾動條「的效果了。
注:鑑於篇幅及我的須要和使用場景,我並沒去實現上面的替換ListBox、TextBox滾動條的效果,只是從邏輯層面上驗證能夠達到預期效果。
後續若是有須要,我會考慮寫一篇文章去實現一下這種效果。
通篇下來,會發現技術層面上算不上太難,難點在於突破常規的思惟束縛。
能夠看到,WinForm並不是不能實現炫麗、更加現代化的效果,不過須要一些想像力支撐、並多付出一些的努力罷了。一樣的,像WPF、像Electron等WebUI,雖然自己就更加現代化,可是想到達到必定的炫麗效果、比較人性化界面,也是須要花費不小的精力的。並非說使用了新的語言、框架就能夠很輕鬆的、或者自動的實現想要的效果。
技術並無先進和落後,只有合適與不合適,由於各有優勢與缺點、各有擅長與不擅長。
因此,對本身掌握的知識多抱有一些信心,釋放本身的想像力,在實踐中提高本身。