明輝手游網(wǎng)中心:是一個(gè)免費(fèi)提供流行視頻軟件教程、在線(xiàn)學(xué)習(xí)分享的學(xué)習(xí)平臺(tái)!

構(gòu)建一個(gè)彈出式圖象按鈕

[摘要]構(gòu)建Windows控件并不是一件特別復(fù)雜的事情。我曾在以前的文章中講過(guò)如何通過(guò)最專(zhuān)業(yè)的技術(shù)來(lái)構(gòu)建復(fù)雜的控件,但這并不意味著構(gòu)建所有控件都是那么復(fù)雜。本文我將用一種曾在我的工作中遇到的簡(jiǎn)單方法來(lái)解決一個(gè)真實(shí)領(lǐng)域中的問(wèn)題。就算你只有一些或者完全沒(méi)有什么構(gòu)建控件的經(jīng)驗(yàn),你也可以用它來(lái)實(shí)現(xiàn)在你的桌面應(yīng)用程...

構(gòu)建Windows控件并不是一件特別復(fù)雜的事情。我曾在以前的文章中講過(guò)如何通過(guò)最專(zhuān)業(yè)的技術(shù)來(lái)構(gòu)建復(fù)雜的控件,但這并不意味著構(gòu)建所有控件都是那么復(fù)雜。本文我將用一種曾在我的工作中遇到的簡(jiǎn)單方法來(lái)解決一個(gè)真實(shí)領(lǐng)域中的問(wèn)題。就算你只有一些或者完全沒(méi)有什么構(gòu)建控件的經(jīng)驗(yàn),你也可以用它來(lái)實(shí)現(xiàn)在你的桌面應(yīng)用程序中加入復(fù)雜的功能。

我需要一個(gè)帶有不同圖象的彈出式按鈕,用于實(shí)現(xiàn)常規(guī)的、mouse-hover和mouse-down狀態(tài)。我可以用一個(gè)常規(guī)的WinForm按鈕來(lái)實(shí)現(xiàn)大多數(shù)我想要的效果,但卻不能實(shí)現(xiàn)給邊框加上顏色。我還想要讓圖象移到按鈕的右邊緣,就象菜單按鈕那樣。確切地說(shuō),我是需要一個(gè)能夠代表其本身功能的菜單按鈕。

你可以用大約150行的代碼來(lái)構(gòu)建這個(gè)控件;最長(zhǎng)的過(guò)程包含約25行代碼。這個(gè)方法是一個(gè)很好的起點(diǎn);你可以給它添加許多性能并可以將它當(dāng)作一個(gè)其他類(lèi)型控件的模式。該過(guò)程的屬性或許是這個(gè)項(xiàng)目中最為復(fù)雜的一個(gè)地方了――對(duì).NET提供的經(jīng)過(guò)深思熟慮的基類(lèi)的一個(gè)確實(shí)的證明。

基本的方法是以一個(gè)已經(jīng)存在的控件開(kāi)始并通過(guò)繼承來(lái)添加或改變其行為?丶腜aint事件允許你在窗體中進(jìn)行隨意繪制。對(duì)listbox或treeview來(lái)說(shuō),完成這個(gè)功能可能需要做很多工作,但對(duì)按鈕來(lái)說(shuō),只需用圖象作為表面就可以了。你可以通過(guò)從Button類(lèi)中派生出你所需要的ImageButton類(lèi),用一個(gè)Button控件的Paint事件來(lái)繪制出適當(dāng)?shù)膱D象。然而,對(duì)于一個(gè)彈出式按鈕來(lái)說(shuō),象Image、FlatStyle和AutoSize這樣的Button屬性是沒(méi)有意義的。作為替代,你可以從Control基類(lèi)中派生它并自己為它加上邊框。這樣做并不需要你編寫(xiě)額外的代碼,它會(huì)生成一個(gè)更有效的控件和一個(gè)用于構(gòu)建其他控件窗體的通用模板。

一個(gè)彈出式按鈕的行為是很簡(jiǎn)單的。它有三種狀態(tài),每種狀態(tài)都帶有一個(gè)邊框和一個(gè)圖象。Control基類(lèi)支持一組可以被覆蓋(override)的Mouse過(guò)程,以及Paint程序。你可以通過(guò)簡(jiǎn)單地從Windows.Forms.Control派生來(lái)開(kāi)始一個(gè)程序。奇怪的是,Control基類(lèi)不是一個(gè)“必須繼承類(lèi)”(通常被成為抽象類(lèi)),就是說(shuō)以該類(lèi)為基類(lèi)進(jìn)行派生時(shí),你無(wú)需覆蓋任何方法。覆蓋是指Windows和.NET允許你在某人或某個(gè)東西(即系統(tǒng))調(diào)用了基類(lèi)的方法時(shí)執(zhí)行你自己的代碼。這一點(diǎn)非常有用。

在繪制時(shí)進(jìn)行選擇

當(dāng)一個(gè)終端用戶(hù)切換到另一個(gè)頁(yè)面時(shí), ImageButton、Windows以及.NET會(huì)通知Control類(lèi)。Control類(lèi)將Windows的信息傳遞給繼承者的OnPaint程序。在編寫(xiě)覆蓋程序時(shí)你可以運(yùn)行自己的代碼,而不需要完全按照基類(lèi)的做法。盡管Control類(lèi)不是一個(gè)抽象基類(lèi),但它自己并不完成任何繪制。然而,在你需要繼承一個(gè)類(lèi)時(shí),――比如Button或Label類(lèi),通常你會(huì)取代基類(lèi)的painting,而不是將它添加到你的程序中。OnPaint 覆蓋中包括一個(gè)對(duì)MyBase的調(diào)用,這不是因?yàn)榛?lèi)需要進(jìn)行處理來(lái)實(shí)現(xiàn)繪制,而是為了給用戶(hù)提供一個(gè)自己的Paint事件。繼承類(lèi)不會(huì)直接代表其基類(lèi)來(lái)觸發(fā)事件,對(duì)MyBase.OnPaint的調(diào)用導(dǎo)致基類(lèi)觸發(fā)客戶(hù)端Paint事件。
這一點(diǎn)會(huì)對(duì)你將來(lái)構(gòu)建控件有所影響,因此為了讓你有更全面的了解我將從另一個(gè)角度對(duì)它進(jìn)行講述。如果你通過(guò)覆蓋一個(gè)OnPaint 來(lái)支持你自己的作品(就是說(shuō)用于一個(gè)標(biāo)準(zhǔn)的Button基類(lèi)),而且你不僅僅想要實(shí)現(xiàn)基類(lèi)所完成繪制,那么你的OnPaint覆蓋中就不應(yīng)該包含MyBase.OnPaint調(diào)用。在這個(gè)場(chǎng)景中,如果你還想為使用派生控件的開(kāi)發(fā)人員提供一個(gè)Paint事件,則必須在基類(lèi)中提供一個(gè)Paint事件聲明。如果基類(lèi)中已存在了一個(gè)Paint事件,你則必須用Shadows關(guān)鍵字來(lái)聲明你自己的事件從而將基類(lèi)的事件隱藏起來(lái)。不要輕易嘗試使用Shadows,因?yàn)樗菀鬃屖褂迷摽丶拈_(kāi)發(fā)人員搞糊涂,雖然在一個(gè)事件中使用這種方法看起來(lái)似乎更安全。

Shadows只是用一個(gè)和基類(lèi)相似的名稱(chēng)向用戶(hù)顯示一種方法的派生版本。它所存在的潛在問(wèn)題是用戶(hù)仍然可以通過(guò)用CType將你的類(lèi)中的對(duì)象轉(zhuǎn)化為基類(lèi)來(lái)得到基類(lèi)中的方法。Control類(lèi)中的一些方法對(duì)ImageButton來(lái)說(shuō)是沒(méi)有用的。比如,不需要Text屬性。你可以在Visual Studio的 Properties窗口中將Control.Text用一個(gè)ReadOnly屬性替換掉,返回一個(gè)空串。

前面這段代碼不會(huì)導(dǎo)致出錯(cuò),但卻不會(huì)真正起什么作用;ImageButton不會(huì)通過(guò)其基類(lèi)的Text屬性來(lái)繪制控件。然而,如果用戶(hù)嘗試填寫(xiě)ImageButton的Text屬性則會(huì)導(dǎo)致產(chǎn)生一個(gè)design-time(編譯)只讀錯(cuò)誤。

最后,通過(guò)將屬性添加到聲明中來(lái)把Text屬性隱藏起來(lái)。它還要求你給用戶(hù)提供一個(gè)新的缺省屬性,否則是無(wú)效的,因?yàn)镃ontrol的缺省屬性是Text。通過(guò)將添加到類(lèi)聲明中來(lái)將DisplayImageIndex屬性作為新的缺省屬性。

涂成藍(lán)色

和菜單按鈕一樣,ImageButton必須帶有不同的圖象和邊框式樣,這取決于鼠標(biāo)的位置。和菜單按鈕不同的是,ImageButton必須能夠獲得焦點(diǎn)并顯示焦點(diǎn)矩形框。所有的特性都必須通過(guò)代碼來(lái)實(shí)現(xiàn),因?yàn)镃ontrol類(lèi)不會(huì)處理。然而,你只需一小段代碼就可以實(shí)現(xiàn)它,就像你從OnPaint過(guò)程中看到的那樣。

你可以通過(guò)OnMouseEnter、Leave、Up和Down覆蓋過(guò)程從系統(tǒng)中獲得鼠標(biāo)通知。你可以象使用一般的mouse事件一樣來(lái)使用它們,但是用覆蓋意味著你能夠在基類(lèi)提供行為之前或之后添加新的行為,或者取代基類(lèi)的行為。通過(guò)設(shè)置一個(gè)MouseButtonState變量,你可以用每個(gè)過(guò)程來(lái)決定將哪個(gè)圖象拖到控制界面。OnMouseDown還會(huì)設(shè)定焦點(diǎn): Overrides Sub OnMouseDown(ByVal ma As _
MouseEventArgs)
MyBase.OnMouseDown(ma)
_MouseButtonState = Down
Me.Focus()
MyBase.Invalidate()
End Sub
Control.Invalidate調(diào)用用于告知基類(lèi)該控件需要被重畫(huà);(lèi)依次調(diào)用覆蓋的OnPaint方法,它通過(guò)PaintEventArgs來(lái)提供一個(gè)GDI+ 圖象對(duì)象。你可以用該對(duì)象共享的DrawImage方法用一行代碼繪制一個(gè)位圖(bitmap),給DrawImage提供圖象、位置和大小,選擇將哪個(gè)圖象繪制到鼠標(biāo)位置。一個(gè)很方便的設(shè)計(jì)態(tài)專(zhuān)用的DisplayImageIndex屬性會(huì)讓用戶(hù)自己選擇將哪種圖象顯示出來(lái)。你可以將兩種屬性用在該方法的聲明中:用于告訴Visual Studio屬性窗口該在哪里列出該屬性,用于在運(yùn)行時(shí)將它隱藏起來(lái)。給DisplayImageIndex值添加一個(gè)枚舉,使用戶(hù)可以通過(guò)簡(jiǎn)單地點(diǎn)擊這個(gè)值來(lái)查看到Down、Up和Hover。DisplayImageIndex使用戶(hù)無(wú)需打開(kāi)ImageList控件來(lái)確保他們選擇了正確的用于Down、Up和Hover的ImageIndex值。

你可以用鼠標(biāo)位置來(lái)選取需要繪制的邊框顏色。當(dāng)焦點(diǎn)集中在控件上時(shí),代碼將邊框厚度設(shè)置為兩個(gè)象素點(diǎn),只用于UP狀態(tài)。建立一個(gè)新的Pen對(duì)象(不要用缺省的系統(tǒng)的畫(huà)筆)畫(huà)出大于一個(gè)象素點(diǎn)的一行。不要忘記在完成時(shí)調(diào)用Dispose方法。你應(yīng)該根據(jù)邊框的寬度調(diào)整邊框矩形的大小,因?yàn)榭丶荒茈S意在窗體以外進(jìn)行繪制。

我從來(lái)不喜歡用按鈕圖象的算法操作來(lái)顯示up、over和down狀態(tài),每個(gè)ImageButton均用了三個(gè)單獨(dú)的位圖。在一個(gè)form中使用許多ImageButtons會(huì)導(dǎo)致產(chǎn)生大量的圖象,因此我給ImageButton提供了一個(gè)ImageList屬性,而不是三個(gè)圖象屬性。將該屬性作為Forms.ImageList來(lái)聲明,則NET和VS.NET IDE會(huì)為你處理大量的工作。你不需要通過(guò)編寫(xiě)代碼來(lái)檢測(cè)ImageList,屬性窗口會(huì)將它顯示出來(lái)。使用ImageList的另一個(gè)好處是它排除了用代碼處理用戶(hù)提供圖象大小的可能性。當(dāng)用戶(hù)以不同的大小加載它時(shí),ImageList代表的是一個(gè)單一大小和比例的圖象。

圖象的大小決定了ImageButton的大小;該控件沒(méi)有AutoSize屬性。假如用戶(hù)試圖通過(guò)拖動(dòng)控件的邊框或通過(guò)屬性窗口來(lái)改變它的大小,則ImageButton會(huì)立即重新設(shè)置為圖象的大小。你可以通過(guò)覆蓋OnSizeChanged過(guò)程來(lái)得到該行為,你還可以用一個(gè)只讀版本將Control類(lèi)的非覆蓋Size屬性隱藏起來(lái)。這給IDE帶來(lái)了一個(gè)問(wèn)題,因?yàn)樗蛄谢疭ize屬性,試著將它設(shè)置到用戶(hù)窗體的"Designer generated code"區(qū)域。添加屬性以避免用戶(hù)在設(shè)計(jì)時(shí)讀取它的屬性序列化時(shí),它提供一個(gè)更友好的工具給用戶(hù)。 (出處:PConline)