前幾篇文章我們一直在討論如何更方便的編輯復雜類型的屬性,在這個過程中我介紹了類型轉換器以 及如何制作自己的類型轉換器來實現屬性值的串行化和實現子屬性的編輯。對於Scope這種級別的復雜屬 性,一個類型轉換器就已經足夠了,但是對於更為復雜的屬性,單單使用類型轉換器已經不足以應付了, 比如我們常用的Font屬性。
在這種情況下,我們就需要提供更為復雜的編輯方式,比如屬性編輯對話框,你還記得Font對話框嗎 ?現在我們就來看看如何實現更復雜的屬性編輯。復雜的屬性編輯器分為兩種類型,一種是彈出式模態對 話框屬性編輯器,一種式下拉式屬性編輯器。如果你還沒有感性的認識的話,可以觀察一下TextBox控件 的屬性,Font屬性的編輯器是模態對話框屬性編輯器,Dock屬性的編輯器是下拉式屬性編輯器。
接下來我們來制作一個模態對話框編輯器,雖然Scope屬性並不復雜,但是為了演示的方便,我們還是 用它來做例子。
首先我們要做一個用來編輯屬性的對話框,在對話框的構造函數裡傳入要編輯的屬性的值。在對話框 類裡,聲明一個Scope類型的私有變量_scope用以保存傳入和編輯後的值。還要增加一個Scope屬性,以便 外部環境能夠獲取編輯後的結果。對話框的外觀如下:
在這個對話框裡,我們要把OK按鈕的DialogResult屬性設為OK(當點擊OK按鈕時,模態對話框關閉, 並返回DialogResult.OK),將Cancel按鈕的DialogResult屬性設為Cancel(當點擊OK按鈕時,模態對話框 關閉,並返回DialogResult.OK)。另外我們要對用戶輸入的值做驗證,以保證Scope的min和max值都是 Int32類型。下邊是對話框的代碼:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; namespace CustomControlSample { public partial class ScopeEditorDialog : Form { private Scope _scope = null; public ScopeEditorDialog(Scope scope) { InitializeComponent(); _scope = scope; textBox1.Text = _scope.Min.ToString(); textBox2.Text = _scope.Max.ToString(); } private void button1_Click(object sender, EventArgs e) { _scope.Min = Convert.ToInt32(textBox1.Text); _scope.Max = Convert.ToInt32(textBox2.Text); } private void textBox1_Validating(object sender, CancelEventArgs e) { try { Int32.Parse(textBox1.Text); } catch (FormatException) { e.Cancel = true; MessageBox.Show("無效的值", "驗證錯誤", MessageBoxButtons.OK, MessageBoxIcon.Error); } } private void textBox2_Validating(object sender, CancelEventArgs e) { try { Int32.Parse(textBox2.Text); } catch (FormatException) { e.Cancel = true; MessageBox.Show("無效的值", "驗證錯誤", MessageBoxButtons.OK, MessageBoxIcon.Error); } } public Scope Scope { get { return _scope; } set { _scope = value; } } } }
每一個屬性的編輯器都是直接或者間接的派生於UITypeEditor。開發環境從來也不會直接調用我們編 寫的模態對話框來編輯屬性,而是調用UITypeEditor的某些虛方法,所以我們還必須提供一個派生於 UITypeEditor的類來與開發環境通信。下邊的代碼實現了Scope的編輯器:
using System; using System.ComponentModel; using System.Drawing.Design; using System.Windows.Forms.Design; using System.Windows.Forms; namespace CustomControlSample { public class ScopeEditor:UITypeEditor { public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context) { if (context != null && context.Instance != null) { return UITypeEditorEditStyle.Modal; } return base.GetEditStyle(context); } public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value) { IWindowsFormsEditorService editorService = null; if (context != null && context.Instance != null && provider != null) { editorService = (IWindowsFormsEditorService) provider.GetService(typeof(IWindowsFormsEditorService)); if (editorService != null) { MyListControl control = (MyListControl) context.Instance; ScopeEditorDialog dlg = new ScopeEditorDialog (control.Scope); if (dlg.ShowDialog()== DialogResult.OK) { value = dlg.Scope; return value; } } } return value; } } }
在這個類裡,我們重寫了兩個方法,一個是GetEditStyle,在這個方法裡,我們通知開發環境,屬性 的編輯器是一個模態對話框。另一個方法是EditValue,這是最核心的方法,在這個方法裡,我們通過上 下文環境獲得了正在編輯的控件的實例,並將實例的Scope屬性傳遞給屬性編輯對話框,顯示對話框供用 戶編輯屬性的值,用戶編輯完屬性的值,並關閉對話框,這時,我們從對話框裡獲取編輯後的結果反會給 開發環境。 編寫完Editor,我們就要將它應用到MyListControl的Scope屬性上,現在的 Scope屬性定義如下:
[Browsable(true)] [Editor(typeof(ScopeEditor),typeof(UITypeEditor))] public Scope Scope { get { return _scope; } set { _scope = value; } }
我們在Scope屬性前加上了[Editor(typeof(ScopeEditor),typeof(UITypeEditor))]元數據。Build工 程,查看實際的效果。在測試工程的窗體上,選中控件,觀察Scope屬性,當我們單擊Scope屬性的值時, 在屬性值的後邊出現了一個按鈕,如圖:
當我們點擊這個按鈕後,彈出了屬性編輯的對話框,如圖:
我們在對話框裡編輯屬性的值,並點擊OK關閉對話框,現在Scope屬性值已經被修改了。