一、簡介
首先,本文假定你已經熟悉VB.net和Visual Studio.NET Windows表單設計器。
在開發定制Windows表單控件時,提供我們自己的下拉框類型編輯器來操作控件的屬性常常是非常方便的。定制的類型編輯器不僅可以提供更為豐富的設計時刻體驗,而且可能成為用戶是否喜歡你的控件的決定因素。
如果你決定創建你自己的下拉式類型編輯器,那麼它應該遵循與內置的下拉框類型編輯器相類似的模式。讓我們以Anchor屬性為例。一種典型的操作該屬性的設計時刻用戶交互描述如下:
· 用戶選擇屬性格子中的Anchor屬性並且點擊屬性格右邊的下拉按鈕。
· 一種良好的圖形控件是下拉框,它能夠允許用戶使用鼠標點擊邊緣或者使用箭頭鍵來高亮某個邊緣並使用空格鍵選擇/取消選擇它。
· 用戶可以通過按下ENTER鍵或點擊下拉控件的外部來接收變化。為了取消這一變化,用戶可以按下ESC鍵。
下面,讓我們來討論具體的實現技術。
二、實現
首先,讓我們構建一個ResourceImageEditor類型編輯器,它允許從當前文件系統中選擇一個圖像文件(就象內置的ImageEditor類一樣)或者從一個程序集的manifest文件中選擇一個圖像資源。而且,在用戶體驗方面,該ResourceImageEditor的行為應該類似於系統內置的類型編輯器。下面是對我們要求的概述:
1. 當用戶從屬性格子中選擇一個屬性時,該格子就會顯示出來—以一個下拉框UI形式顯示可以編輯的屬性。
2. 當點擊下拉按鈕時,當前程序集中的所有圖像資源將顯示出來。
3. 當用戶選擇一個圖像資源項,相應的圖像即可以從程序集中進行加載。
4. 允許選擇一個圖像文件,並且在下拉列表框中的最後一項將標記為“Browse...”。當用戶點按“Browse...”項,將顯示經典的打開文件對話框,用戶能夠從中選擇一個圖像文件。
5. 通過單擊鼠標或使用箭頭鍵高亮某項並按回車鍵實際選擇它從而允許用戶從該下拉列表框中選擇一項。這個下拉選擇可以通過按下ESC鍵取消。
ResourceImageEditor是一個類型編輯器,因此它直接或間接地派生自System.Drawing.Design.UITypeEditor類。我決定從內置的System.Drawing.Design.ImageEditor類進行派生是因為它已經實現了圖像文件選擇功能。也就是說,ImageEditor.EditValue實現將顯示一個文件打開對話框以允許用戶從文件系統中選擇一個圖像文件。然後,從我的派生類中調用這一功能只需要簡單地調用MyBase.EditValue即可。
為了實現上面第一個要求(在屬性格子中顯示下拉箭頭按鈕),我必須重載GetEditStyle方法以從UITypeEditorEditStyle枚舉中返回適當的常數:
Public Overloads Overrides Function GetEditStyle( _
ByVal context As ITypeDescriptorContext) As UITypeEditorEditStyle
Return UITypeEditorEditStyle.DropDown
End Function
為了顯示圖像資源列表,我必須列舉一個給定程序集中的所有資源並且僅在列表中顯示圖像資源。為了簡化,我決定使用一種簡單的約定:當一個資源名以一個有效圖像文件擴展名(.bmp,.jpg,.gif...)結束時,
我們就認為這是一種圖像資源,並且把它包括到該下拉列表框中。而且,我使用圖像資源名的集合來填充這個下拉ListBox控件,後面詳預以詳述。
開始時,被枚舉以查詢圖像資源的程序集就是包含ResourceImageEditor類的程序集。然而,我們可以通過把ResourceImageEditor.ResourceAssembly屬性設置為任何有效的System.Reflection.Assembly參考來改變它。
鼠標接口工作得很好。然而,當我使用鍵盤選擇一項然後按下回車鍵時,該下拉列表框消失,而且我的選定內容丟失了(也就是說,前一個選擇圖像並沒有改變)。我很快發現,當按下回車鍵時,該ListBox並沒有生成KeyDown事件。
盡管ESC鍵也產生KeyDown事件,但這不是一個問題;因為該下拉列表框會被自動關閉,而且我不必處理當前選擇項。
很明顯,在ListBox控件能夠處理它們之前,這個屬性格“屏蔽”了ENTER和ESC鍵。
為了簡化而且還要解決問題,我要使用ProcessDialogKey方法。在消息預處理期間(處理對話字符,例如TAB、RETURN、ESCAPE和箭頭鍵)時,調用這個方法。這個方法是在System.Windows.Forms.Control類內聲明的—它簡單地把該調用代理給該控件的父級(如果有的話)。我已經子類化該ListBox控件,並且重載了ProcessDialogKey方法來攔截回車鍵,如下所示:
Protected Overrides Function ProcessDialogKey(ByVal keyData As Keys) As Boolean
If keyData = System.Windows.Forms.Keys.Return Then
RaiseEvent EntERPressed(Me, EventArgs.Empty)
Return True 'True意味著我們已經處理了相應的鍵
Else
Return MyBase.ProcessDialogKey(keyData)
End If
End Function
不是從ProcessDialogKey實現內部生成KeyDown事件,我決定使用一種更為直接的方式:EnterPressed事件。為了,我修改了ResourceImageEditor.EditValue的實現以處理這一事件(而不是KeyDown事件),而且一切都非常順利。
你可以使用這一技術來攔截任何Control派生的類(你使用它來實現你的類型編輯器中的下拉UI)中的ENTER鍵。
當用戶從列表框中選擇一個圖像資源名時,該圖像應該即可從給定的程序集中的manifest文件中加載。這是在LoadResourceImage方法內實現的:
Private Function LoadResourceImage(ByVal resourceName As String) As Image
Debug.Assert(Not resourceName Is Nothing)
Dim ImageStream As System.IO.Stream = Me.ResourceAssembly.GetManifestResourceStream(resourceName)
Return System.Drawing.Bitmap.FromStream(ImageStream)
End Function
下拉用戶接口是通過在重載的EditValue方法內動態地創建和填充一個ListBox控件實現的。編輯器也處理由ListBox生成的Click和KeyDown事件,因為這是攔截ENTER和ESC鍵所必需的。下列偽碼顯示了在EditValue方法中的實現邏輯:
Public Overloads Overrides Function EditValue(...)
'存儲上下文信息以用於下拉ListBox事件處理器。
'創建並使用可用的圖像資源名填充該ListBox。
'添加我們的特殊“Browse...”項。
'綁定ListBox事件。
'在一個下拉窗口中顯示該ListBox。
End Function
三、幾個關鍵問題與解案
為了開發ResourceImageEditor,我創建了一個重載Image屬性的MyPictureBox(派生自System.Windows.Forms.PictureBox),以便把ResourceImageEditor指定為該Image屬性的類型編輯器。
然後,我編譯這個控件的代碼。之後,就可以把該MyPictureBox控件放到一個表單上並且調用下拉框用戶接口......