構建一個彈出式圖象按鈕
2024-07-21 02:22:26
供稿:網友
 
構建windows控件并不是一件特別復雜的事情。我曾在以前的文章中講過如何通過最專業的技術來構建復雜的控件,但這并不意味著構建所有控件都是那么復雜。本文我將用一種曾在我的工作中遇到的簡單方法來解決一個真實領域中的問題。就算你只有一些或者完全沒有什么構建控件的經驗,你也可以用它來實現在你的桌面應用程序中加入復雜的功能。
我需要一個帶有不同圖象的彈出式按鈕,用于實現常規的、mouse-hover和mouse-down狀態。我可以用一個常規的winform按鈕來實現大多數我想要的效果,但卻不能實現給邊框加上顏色。我還想要讓圖象移到按鈕的右邊緣,就象菜單按鈕那樣。確切地說,我是需要一個能夠代表其本身功能的菜單按鈕。
你可以用大約150行的代碼來構建這個控件;最長的過程包含約25行代碼。這個方法是一個很好的起點;你可以給它添加許多性能并可以將它當作一個其他類型控件的模式。該過程的屬性或許是這個項目中最為復雜的一個地方了――對.net提供的經過深思熟慮的基類的一個確實的證明。
基本的方法是以一個已經存在的控件開始并通過繼承來添加或改變其行為。控件的paint事件允許你在窗體中進行隨意繪制。對listbox或treeview來說,完成這個功能可能需要做很多工作,但對按鈕來說,只需用圖象作為表面就可以了。你可以通過從button類中派生出你所需要的imagebutton類,用一個button控件的paint事件來繪制出適當的圖象。然而,對于一個彈出式按鈕來說,象image、flatstyle和autosize這樣的button屬性是沒有意義的。作為替代,你可以從control基類中派生它并自己為它加上邊框。這樣做并不需要你編寫額外的代碼,它會生成一個更有效的控件和一個用于構建其他控件窗體的通用模板。
一個彈出式按鈕的行為是很簡單的。它有三種狀態,每種狀態都帶有一個邊框和一個圖象。control基類支持一組可以被覆蓋(override)的mouse過程,以及paint程序。你可以通過簡單地從windows.forms.control派生來開始一個程序。奇怪的是,control基類不是一個“必須繼承類”(通常被成為抽象類),就是說以該類為基類進行派生時,你無需覆蓋任何方法。覆蓋是指windows和.net允許你在某人或某個東西(即系統)調用了基類的方法時執行你自己的代碼。這一點非常有用。
在繪制時進行選擇
當一個終端用戶切換到另一個頁面時, imagebutton、windows以及.net會通知control類。control類將windows的信息傳遞給繼承者的onpaint程序。在編寫覆蓋程序時你可以運行自己的代碼,而不需要完全按照基類的做法。盡管control類不是一個抽象基類,但它自己并不完成任何繪制。然而,在你需要繼承一個類時,――比如button或label類,通常你會取代基類的painting,而不是將它添加到你的程序中。onpaint 覆蓋中包括一個對mybase的調用,這不是因為基類需要進行處理來實現繪制,而是為了給用戶提供一個自己的paint事件。繼承類不會直接代表其基類來觸發事件,對mybase.onpaint的調用導致基類觸發客戶端paint事件。
這一點會對你將來構建控件有所影響,因此為了讓你有更全面的了解我將從另一個角度對它進行講述。如果你通過覆蓋一個onpaint 來支持你自己的作品(就是說用于一個標準的button基類),而且你不僅僅想要實現基類所完成繪制,那么你的onpaint覆蓋中就不應該包含mybase.onpaint調用。在這個場景中,如果你還想為使用派生控件的開發人員提供一個paint事件,則必須在基類中提供一個paint事件聲明。如果基類中已存在了一個paint事件,你則必須用shadows關鍵字來聲明你自己的事件從而將基類的事件隱藏起來。不要輕易嘗試使用shadows,因為它容易讓使用該控件的開發人員搞糊涂,雖然在一個事件中使用這種方法看起來似乎更安全。
shadows只是用一個和基類相似的名稱向用戶顯示一種方法的派生版本。它所存在的潛在問題是用戶仍然可以通過用ctype將你的類中的對象轉化為基類來得到基類中的方法。control類中的一些方法對imagebutton來說是沒有用的。比如,不需要text屬性。你可以在visual studio的 properties窗口中將control.text用一個readonly屬性替換掉,返回一個空串。用戶可能會覺得很麻煩,但這樣卻能避免出現一些問題:dim pop1 as new imagebutton
ctype(pop1, control).text = "hi"
 
前面這段代碼不會導致出錯,但卻不會真正起什么作用;imagebutton不會通過其基類的text屬性來繪制控件。然而,如果用戶嘗試填寫imagebutton的text屬性則會導致產生一個design-time(編譯)只讀錯誤:dim pop1 as new imagebutton
pop1.text = "hi" 'error
 
最后,通過將<browsable(false)>屬性添加到聲明中來把text屬性隱藏起來。它還要求你給用戶提供一個新的缺省屬性,否則是無效的,因為control的缺省屬性是text。通過將<defaultproperty("displayimageindex")>添加到類聲明中來將displayimageindex屬性作為新的缺省屬性。
涂成藍色
和菜單按鈕一樣,imagebutton必須帶有不同的圖象和邊框式樣,這取決于鼠標的位置。和菜單按鈕不同的是,imagebutton必須能夠獲得焦點并顯示焦點矩形框。所有的特性都必須通過代碼來實現,因為control類不會處理。然而,你只需一小段代碼就可以實現它,就像你從onpaint過程中看到的那樣。 
你可以通過onmouseenter、leave、up和down覆蓋過程從系統中獲得鼠標通知。你可以象使用一般的mouse事件一樣來使用它們,但是用覆蓋意味著你能夠在基類提供行為之前或之后添加新的行為,或者取代基類的行為。通過設置一個mousebuttonstate變量,你可以用每個過程來決定將哪個圖象拖到控制界面。onmousedown還會設定焦點: overrides sub onmousedown(byval ma as _
 mouseeventargs)
 mybase.onmousedown(ma)
 _mousebuttonstate = down
 me.focus()
 mybase.invalidate()
end sub
 
control.invalidate調用用于告知基類該控件需要被重畫。基類依次調用覆蓋的onpaint方法,它通過painteventargs來提供一個gdi+ 圖象對象。你可以用該對象共享的drawimage方法用一行代碼繪制一個位圖(bitmap),給drawimage提供圖象、位置和大小,選擇將哪個圖象繪制到鼠標位置。一個很方便的設計態專用的displayimageindex屬性會讓用戶自己選擇將哪種圖象顯示出來。你可以將兩種屬性用在該方法的聲明中:<category ("design")>用于告訴visual studio屬性窗口該在哪里列出該屬性,<designonly(true)>用于在運行時將它隱藏起來。給displayimageindex值添加一個枚舉,使用戶可以通過簡單地點擊這個值來查看到down、up和hover。displayimageindex使用戶無需打開imagelist控件來確保他們選擇了正確的用于down、up和hover的imageindex值。
你可以用鼠標位置來選取需要繪制的邊框顏色。當焦點集中在控件上時,代碼將邊框厚度設置為兩個象素點,只用于up狀態。建立一個新的pen對象(不要用缺省的系統的畫筆)畫出大于一個象素點的一行。不要忘記在完成時調用dispose方法。你應該根據邊框的寬度調整邊框矩形的大小,因為控件不能隨意在窗體以外進行繪制。
我從來不喜歡用按鈕圖象的算法操作來顯示up、over和down狀態,每個imagebutton均用了三個單獨的位圖。在一個form中使用許多imagebuttons會導致產生大量的圖象,因此我給imagebutton提供了一個imagelist屬性,而不是三個圖象屬性。將該屬性作為forms.imagelist來聲明,則net和vs.net ide會為你處理大量的工作。你不需要通過編寫代碼來檢測imagelist,屬性窗口會將它顯示出來。使用imagelist的另一個好處是它排除了用代碼處理用戶提供圖象大小的可能性。當用戶以不同的大小加載它時,imagelist代表的是一個單一大小和比例的圖象。
圖象的大小決定了imagebutton的大小;該控件沒有autosize屬性。假如用戶試圖通過拖動控件的邊框或通過屬性窗口來改變它的大小,則imagebutton會立即重新設置為圖象的大小。你可以通過覆蓋onsizechanged過程來得到該行為,你還可以用一個只讀版本將control類的非覆蓋size屬性隱藏起來。這給ide帶來了一個問題,因為他要序列化size屬性,試著將它設置到用戶窗體的"designer generated code"區域。添加<designerserializationvisibility(designerserializationvisibility.hidden)>屬性以避免用戶在設計時讀取它的屬性序列化時,它提供一個更友好的工具給用戶。國內最大的酷站演示中心!