摘要:Designing and Implement Lookup Control for Windows Forms
Designing and Implement Lookup Control for Windows Forms
文/黃忠成
What’s Lookup Control
前篇所開發的OrpButtonEdit控件,雖然已經達到了初步的需求,但使用這個控件,設計師仍然必須自行設計開出的查詢視窗、處理選取的資料、回填至ButtonEdit控件中等課題,然而這些動作都是可規格化的,本文中所開發的Lookup Control將針對此問題,做出更便利的選取資料介面。事實上,Lookup Control在很早期的商用應用程式就已出現,她是一個類似ComboBox的控件,只是拉出的視窗不僅僅顯示單欄資料,而是顯示出一個Grid,讓使用者可以看到一整筆資料,而非僅僅單一欄位,見圖1。
圖1
設計這樣的控件,有兩個不可缺的關鍵控件,一是DataGridView控件,用來顯示可選取的資料,二是Form控件,DataGridView控件必須存活於Container Control中,例如Panel或是Form,多數情況下,為了得到更大的控制權,3rd Patrt廠商多會選擇使用Form而非Panel,做為DataGridView控件的Container。
Requirement
Lookup Control的需求很簡單,其必須提供DataSource/DataMember等資料繫結所需的屬性,讓設計者設定欲顯示的資料,同時也必須提供一個Columns Collection,允許設計者選取欲列出的欄位。不過由於列出的欄位數量不等,所以可能會出現拉下的視窗太小,不足以顯示所有欄位的問題,因此,Lookup Control必須提供一個屬性,讓設計者可以設定拉下視窗的寬度,至於高度,就不需要設計者插手,由Lookup Control視目前視窗的高度來計算最佳顯示高度即可。
Problem
Lookup Control唯一會遭遇的技術困難是,Form在Windows Forms架構中屬於容器型控件,每個Form都是單獨的個體,而Lookup Control所拉出的Form,必須受控於Lookup Control所在的Form,也就是當Lookup Control所在的Form移動時,這個拉下的Form也要跟著移動,這個問題有兩種解法,一種是MDI介面,不過此種方法雖可達到目的,但卻會引發其它的問題,就控件角度來說,我們不應該要求放Lookup Control的Form一定要是MDI Parent,就UI角度而言,變成MDI介面後會有許多限制。因此能用的方法只剩一個,那就是Form所提供的AddOwnedForm函式,呼叫此函式將欲受此Form管轄的Form傳入,就可以解決此處所遭遇的問題。
Designing
曾看過『深入剖析 ASP.NET組件設計』一書的讀者,應該都還記得,我於該書中撰寫了一個WebComboBox控件,於其中加入了ItemBuilder概念,允許設計師以Plug-In的方式,改變下拉視窗中的內容。現在,我將這個概念運用於此Lookup Control中,讓Lookup Control的層級更抽象化,不僅可以拉下DataGridView控件,也可以拉下各式各樣的控件,圖2是此控件的設計圖。
圖2
這張設計圖中,披露了兩個主要的元素,一是OrpCustomEmbedControlEdit,這是一個繼承自OrpCustomButtonEdit的控件,她負責建立下拉視窗,也就是Form容器,並呼叫第二個元素:OrpEmbedEditControl來填入容器中的內容,OrpEmbedEditControl是一個元件,其定義如程式1。
程式1
public abstract class EmbedEditControl : Component { private Form _clientForm; private OrpCustomEmbedControlEdit _editControl; private int _clientFormWidth = -1; protected Form ClientForm { get { return _clientForm; } } [Category("Appearance")] public int ClientFormWidth { get { return _clientFormWidth; } set { _clientFormWidth = value; } } [Browsable(false)] public OrpCustomEmbedControlEdit EditControl { get { return _editControl; } } public virtual void InitializeControl(Form clientForm, OrpCustomEmbedControlEdit editControl) { _clientForm = clientForm; _editControl = editControl; } public abstract void ParseValue(object value); public abstract object GetInputValue(); public abstract void ClientFormClosed(); public void CloseClientForm(bool isCancel) { EditControl.CloseClientForm(isCancel); } } |
如你所見,這是一個抽象類別,其中定義了InitializeControl、ParseValue、GetInputValue、ClientFormClosed等函式,當OrpCustomEmbedControlEdit啟動下拉動作時,會建立一個Form, 然後呼叫InitializeControl函式,OrpEmbedEditControl必須在此將欲顯示於該下拉視窗中的控件填入,接著 ParseValue函式會被呼叫,此處必須依據傳入的值,調整視窗的內容,讓使用者可以看到原本所選取的值,然後必須處理選取資料的動作,當使用者選取 資料後,下拉視窗會被關閉,此時GetInputValue函式會被呼叫,其必須傳回使用者所選取的值,最後ClientFormClosed函式會被呼 叫,此處可以進行視窗關閉後的後續工作,整個流程圖示如圖3。
圖3
Implement
完成了設計圖後,實作就不難了,OrpCustomEmbedControlEdit的工作在於建立下拉視窗,然後呼叫EmbedEditControl元件來填入內容物,這裡會遭遇到一個實作上的困擾,就是何時關閉視窗?這有幾種情況,一是使用者在拉下視窗後,又按下了下拉按鈕,此時自然得關閉視窗,這是Cancel模式,使用者選取的值不會填回OrpCustomEmbedControlEdit中。二是使用者於拉下視窗後,將焦點移到其它控件上,此時一樣視為Cancel模式,關閉視窗。三是使用者調整了含有OrpCustomEmbedControlEdit控件Form的大小,或是於其上點選了滑鼠,這一樣視為Cacnel模式。程式2為OrpCustomEmbedControlEdit的原始碼列表,讀者可於其中看到處理視窗何時開啟、何時關閉的程式碼。
程式2
[ToolboxItem(false)] public class OrpCustomEmbedControlEdit : OrpCustomButtonEdit { private EmbedEditControl _embedEditControl = null; private Form _clientForm = null; private bool _skipLostFocus = false; private int _clientFormWidth = -1; private DateTime _closeTime = DateTime.Now; [Category("Appearance")] public int ClientFormWidth { get { return _clientFormWidth; } set { _clientFormWidth = value; } } protected Form ClientForm { get { if (_clientForm == null) _clientForm = CreateClientForm(); return _clientForm; } } protected bool Droped { get { return (_clientForm != null); } } protected EmbedEditControl EmbedEditControl { get { return _embedEditControl; } set { _embedEditControl = value; } } protected virtual Form CreateClientForm() { return new Form(); } protected internal virtual void CloseClientForm(bool isCancel) { if (_clientForm != null) { Form ownerForm = FindForm(); if (ownerForm != null) { ownerForm.MouseClick -= new MouseEventHandler(ownerForm_MouseClick); ownerForm.Activated -= new EventHandler(ownerForm_Activated); ownerForm.Resize -= new EventHandler(ownerForm_Resize); ownerForm.RemoveOwnedForm(_clientForm); } if (EmbedEditControl != null) { if (!isCancel) Text = (string)EmbedEditControl.GetInputValue(); EmbedEditControl.ClientFormClosed(); } _clientForm.Close(); _clientForm.Dispose(); _clientForm = null; } } private void ShowClientForm() { Point pt = PointToScreen(new Point(Left, Top)); ClientForm.Location = new Point(pt.X - Left - 2, pt.Y - Top + Height - 1); ClientForm.Width = Width; ClientForm.Height = Screen.PrimaryScreen.Bounds.Height - ClientForm.Top - 30; ClientForm.FormBorderStyle = FormBorderStyle.None; ClientForm.Font = (Font)Font.Clone(); ClientForm.BackColor = SystemColors.Window; if (ClientForm.Height > 160) ClientForm.Height = 160; ClientForm.StartPosition = FormStartPosition.Manual; ClientForm.ShowInTaskbar = false; Form ownerForm = FindForm(); if (ownerForm != null) { ownerForm.AddOwnedForm(ClientForm); ownerForm.MouseClick += new MouseEventHandler(ownerForm_MouseClick); ownerForm.Activated += new EventHandler(ownerForm_Activated); ownerForm.Resize += new EventHandler(ownerForm_Resize); } if (EmbedEditControl != null && EmbedEditControl.ClientFormWidth != -1) ClientForm.Width = EmbedEditControl.ClientFormWidth; else if (ClientFormWidth != -1) ClientForm.Width = ClientFormWidth; } void ownerForm_Resize(object sender, EventArgs e) { CloseClientForm(true); } void ownerForm_Activated(object sender, EventArgs e) { if (((Form)sender).ActiveControl != this) CloseClientForm(true); } protected override void OnLostFocus(EventArgs e) { base.OnLostFocus(e); if (Droped) { if (_skipLostFocus) _skipLostFocus = false; else CloseClientForm(true); _closeTime = DateTime.Now; } } void ownerForm_MouseClick(object sender, MouseEventArgs e) { CloseClientForm(true); } protected override void EmbedButtonClick(EventArgs args) { if (Droped) CloseClientForm(false); else { TimeSpan ts = DateTime.Now - _closeTime; if (ts.TotalMilliseconds > 200) { _skipLostFocus = true; ShowClientForm(); if (EmbedEditControl != null) { EmbedEditControl.InitializeControl(ClientForm, this); EmbedEditControl.ParseValue(Text); } ClientForm.Visible = true; } } } } |
OrpCustomEmbedControlEdit控件不是一個可顯示於Toolbox Pattern上的控件,其繼承者:OrpEmbedControlEdit才是。
程式3
[ToolboxItem(true)] public class OrpEmbedControlEdit : OrpCustomEmbedControlEdit { [Category("Behavoir")] public EmbedEditControl EditControl { get { return EmbedEditControl; } set { EmbedEditControl = value; } } } |
Implement ComboBox
完成了OrpCustomEmbedControlEdit這個基底控件後,現在我們可以將焦點放在如何設計可用的EmbedEditControl元件:一個類似ComboBox的控件,她與一般的ComboBox控件不同的是,其內容是可以切換的,舉個例來說,設計師可以放一個OrpEmbedControlEdit控件到Form上,放兩個ListEmbedEditControl元件到Form上,此時該OrpEmbedControlEdit可以動態的切換要使用那個ListEmbedEditControl來顯示可選取的資料,如圖4。
圖4
聰明的你,是否看出OrpEmbedEditControl這個設計的真正意含?是的!可動態切換的下拉視窗內容,可以讓設計師只用一個控件,應對不同的情況。程式4是ListEmbedEditControl元件的原始碼。
程式4
using System; using System.Drawing.Design; using System.ComponentModel; using System.Collections; using System.Collections.Generic; using System.Text; using System.Windows.Forms; namespace LookupComboBox { [TypeConverter(typeof(ListItemConverter)), Serializable] public sealed class ListItem { private string _text, _value; public string Text { get { return _text; } set { _text = value; } } public string Value { get { return _value; } set { _value = value; } } public ListItem(string text, string value) { _text = text; _value = value; } public ListItem() { } } [Serializable] public class ListItems : List<ListItem> { public int FindValue(string value) { for (int i = 0; i < Count; i++) { if (this[i].Value.Equals(value)) return i; } return -1; } } public class ListEmbedEditControl : EmbedEditControl { private ListItems _items; private ListBox _innerListBox = null; private object _dataSource; private string _displayMember, _valueMember; [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] [Category("Data")] public ListItems Items { get { if (_items == null) _items = new ListItems(); return _items; } } [AttributeProvider(typeof(IListSource))] [Category("Data")] public object DataSource { get { return _dataSource; } set { if (((value != null) && !(value is IList)) && !(value is IListSource)) throw new ArgumentException("only implement IList or IListSource can be set."); _dataSource = value; } } [DefaultValue(""), TypeConverter("System.Windows.Forms.Design.DataMemberFieldConverter, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"), Editor("System.Windows.Forms.Design.DataMemberFieldEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))] [Category("Data")] public string DisplayMember { get { return _displayMember; } set { _displayMember = value; } } [DefaultValue(""), TypeConverter("System.Windows.Forms.Design.DataMemberFieldConverter, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"), Editor("System.Windows.Forms.Design.DataMemberFieldEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))] [Category("Data")] public string ValueMember { get { return _valueMember; } set { _valueMember = value; } } public override void InitializeControl(Form clientForm, OrpCustomEmbedControlEdit editControl) { base.InitializeControl(clientForm, editControl); _innerListBox = new ListBox(); _innerListBox.Click += new EventHandler(_innerListBox_Click); _innerListBox.KeyDown += new KeyEventHandler(_innerListBox_KeyDown); _innerListBox.Dock = DockStyle.Fill; if (DataSource == null) { foreach (ListItem item in Items) _innerListBox.Items.Add(item); _innerListBox.DisplayMember = "Text"; _innerListBox.ValueMember = "Value"; } else { _innerListBox.DataSource = DataSource; _innerListBox.DisplayMember = DisplayMember; _innerListBox.ValueMember = ValueMember; } _innerListBox.BorderStyle = BorderStyle.Fixed3D; clientForm.Controls.Add(_innerListBox); } void _innerListBox_KeyDown(object sender, KeyEventArgs e) { if (e.KeyCode == Keys.Return) CloseClientForm(false); else if (e.KeyCode == Keys.Escape) CloseClientForm(true); } void _innerListBox_Click(object sender, EventArgs e) { CloseClientForm(false); } public override void ParseValue(object value) { int index = Items.FindValue((string)value); if (index != -1) _innerListBox.SelectedIndex = index; } public override object GetInputValue() { if (_innerListBox != null && _innerListBox.SelectedItem != null) { if (_innerListBox.SelectedItem is ListItem) return ((ListItem)_innerListBox.SelectedItem).Value; else if(_innerListBox.SelectedValue != null) return _innerListBox.SelectedValue.ToString(); } return string.Empty; } public override void ClientFormClosed() { if (_innerListBox != null) { _innerListBox.Click -= new EventHandler(_innerListBox_Click); _innerListBox.KeyDown -= new KeyEventHandler(_innerListBox_KeyDown); } } } [ToolboxItem(true)] public class OrpComboBox : OrpCustomEmbedControlEdit { [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] [Category("Data")] public ListItems Items { get { return ((ListEmbedEditControl)EmbedEditControl).Items; } } [AttributeProvider(typeof(IListSource))] [Category("Data")] public object DataSource { get { return ((ListEmbedEditControl)EmbedEditControl).DataSource; } set { ((ListEmbedEditControl)EmbedEditControl).DataSource = value; } } [DefaultValue(""), TypeConverter("System.Windows.Forms.Design.DataMemberFieldConverter, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"), Editor("System.Windows.Forms.Design.DataMemberFieldEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))] [Category("Data")] public string DisplayMember { get { return ((ListEmbedEditControl)EmbedEditControl).DisplayMember; } set { ((ListEmbedEditControl)EmbedEditControl).DisplayMember = value; } } [DefaultValue(""), TypeConverter("System.Windows.Forms.Design.DataMemberFieldConverter, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"), Editor("System.Windows.Forms.Design.DataMemberFieldEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))] [Category("Data")] public string ValueMember { get { return ((ListEmbedEditControl)EmbedEditControl).ValueMember; } set { ((ListEmbedEditControl)EmbedEditControl).ValueMember = value; } } public OrpComboBox() : base() { EmbedEditControl = new ListEmbedEditControl(); } } } |
關於ListItems、DesignerSerializationVisibility及TypeConverter 部份,請參考拙著:『深入剖析 ASP.NET組件設計』一書,此處就不再贅述。ListEmbedEditControl元件的重點只有一個,那就是 InitializeControl函式,此處建立了一個ListBox控件,並放入由OrpCustomEmbedControlEdit所傳入的 Form中,剩下的動作就是如何與其互動罷了,圖5是執行畫面。
你也可以使用前面所開發的OrpEmbedControlEdit控件,而非OrpComboBox(這是一個整合了OrpEmbedControlEdit控件及ListEmbedEditControl元件的控件),圖6是其設計時期畫面。
圖6
Implement LookupEdit
如果你可以看懂ListEmbedEditControl元件,那麼接下來的GridEmbedEditControl元件也就不難了,重點同樣在InitializeControl函式,只是從ListBox變成DataGridView而已。
程式5
using System; using System.Drawing; using System.Drawing.Design; using System.ComponentModel; using System.Collections; using System.Collections.Generic; using System.Text; using System.Windows.Forms; namespace LookupComboBox { [TypeConverter(typeof(LookupColumnItemConverter)), Serializable] public class LookupColumnItem { private string _header = string.Empty, _displayMember; private int _width; [NonSerialized] private LookupColumnItems _owner; protected internal LookupColumnItems Owner { get { return _owner; } set { _owner = value; } } public string Header { get { return _header; } set { _header = value; } } [TypeConverter(typeof(LookupColumnNameConverter))] public string DisplayMember { get { return _displayMember; } set { _displayMember = value; if (Header == string.Empty) Header = value; } } public int Width { get { return _width; } set { _width = value; } } public LookupColumnItem(string header, string displayMember, int width) { _header = header; _displayMember = displayMember; _width = width; } public LookupColumnItem() { } } [Serializable] public class LookupColumnItems : CollectionBase { private GridEmbedEditControl _owner; public LookupColumnItem this[int index] { get { return (LookupColumnItem)base.List[index]; } set { base.List[index] = value; } } internal GridEmbedEditControl Owner { get { return _owner; } } public void Add(LookupColumnItem value) { ((IList)this).Add(value); } public void AddRange(LookupColumnItem[] values) { foreach (LookupColumnItem item in values) Add(item); } protected override void OnInsertComplete(int index, object value) { base.OnInsertComplete(index, value); ((LookupColumnItem)value).Owner = this; } public LookupColumnItems(GridEmbedEditControl owner) : base() { _owner = owner; } } public class GridEmbedEditControl : EmbedEditControl { private object _dataSource; private string _dataMember; private BindingSource _bindingSource; private LookupColumnItems _items; private DataGridView _gridView = null; [AttributeProvider(typeof(IListSource))] [Category("Data")] public object DataSource { get { return _dataSource; } set { if (((value != null) && !(value is IList)) && !(value is IListSource)) throw new ArgumentException("only implement IList or IListSource can be set."); _dataSource = value; } } [TypeConverter(typeof(DataMemberConverter))] [Category("Data")] public string DataMember { get { return _dataMember; } set { _dataMember = value; } } [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] [Category("Data")] public LookupColumnItems Items { get { if (_items == null) _items = new LookupColumnItems(this); return _items; } } public override void InitializeControl(Form clientForm, OrpCustomEmbedControlEdit editControl) { bool hasCustomColumnSize = false; base.InitializeControl(clientForm, editControl); _bindingSource = new BindingSource(); _bindingSource.DataSource = _dataSource; _bindingSource.DataMember = _dataMember; _gridView = new DataGridView(); _gridView.AutoGenerateColumns = false; _gridView.AllowUserToAddRows = false; _gridView.AllowUserToDeleteRows = false; _gridView.AllowUserToOrderColumns = false; _gridView.AllowUserToResizeColumns = false; _gridView.AllowUserToResizeRows = false; _gridView.BorderStyle = System.Windows.Forms.BorderStyle.None; _gridView.CellBorderStyle = System.Windows.Forms.DataGridViewCellBorderStyle.None; _gridView.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; _gridView.GridColor = System.Drawing.SystemColors.Control; _gridView.MultiSelect = false; _gridView.ReadOnly = true; _gridView.RowHeadersVisible = false; _gridView.RowHeadersWidthSizeMode = System.Windows.Forms.DataGridViewRowHeadersWidthSizeMode.DisableResizing; _gridView.RowTemplate.Height = 24; _gridView.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect; _gridView.TabIndex = 1; _gridView.Dock = DockStyle.Fill; _gridView.BorderStyle = BorderStyle.Fixed3D; _gridView.CellClick += new DataGridViewCellEventHandler(_gridView_CellClick); _gridView.KeyDown += new KeyEventHandler(_gridView_KeyDown); foreach (LookupColumnItem item in Items) { DataGridViewTextBoxColumn column = new DataGridViewTextBoxColumn(); column.HeaderText = item.Header; column.DataPropertyName = item.DisplayMember; if (column.Width != 0) { hasCustomColumnSize = true; column.Width = item.Width; } _gridView.Columns.Add(column); } if (!hasCustomColumnSize) _gridView.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells; _gridView.DataSource = _bindingSource; _gridView.Font = (Font)editControl.Font.Clone(); clientForm.Controls.Add(_gridView); clientForm.ActiveControl = _gridView; } void _gridView_KeyDown(object sender, KeyEventArgs e) { if (e.KeyCode == Keys.Return) CloseClientForm(false); else if (e.KeyCode == Keys.Escape) CloseClientForm(true); } void _gridView_CellClick(object sender, DataGridViewCellEventArgs e) { CloseClientForm(false); } public override void ParseValue(object value) { if (Items.Count > 0) { try { int index = _bindingSource.Find(Items[0].DisplayMember, value); if (index != -1) _bindingSource.Position = index; else _bindingSource.Position = 0; } catch (Exception) { } } } public override object GetInputValue() { if (Items.Count > 0) { object data = _bindingSource.Current; if (data != null) { PropertyDescriptor pd = TypeDescriptor.GetProperties(data).Find(Items[0].DisplayMember, false); if (pd != null) { object value = pd.GetValue(data); if (value != null) return value.ToString(); } } } return string.Empty; } public override void ClientFormClosed() { if (_gridView != null) { _gridView.CellClick -= new DataGridViewCellEventHandler(_gridView_CellClick); _gridView.KeyDown -= new KeyEventHandler(_gridView_KeyDown); _gridView.DataSource = null; if(_bindingSource != null) _bindingSource.Dispose(); } } } [ToolboxItem(true)] public class OrpLookupEdit : OrpCustomEmbedControlEdit { [AttributeProvider(typeof(IListSource))] [Category("Data")] public object DataSource { get { return ((GridEmbedEditControl)EmbedEditControl).DataSource; } set { ((GridEmbedEditControl)EmbedEditControl).DataSource = value; } } [TypeConverter(typeof(DataMemberConverter))] [Category("Data")] public string DataMember { get { return ((GridEmbedEditControl)EmbedEditControl).DataMember; } set { ((GridEmbedEditControl)EmbedEditControl).DataMember = value; } } [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] [Category("Data")] public LookupColumnItems Items { get { return ((GridEmbedEditControl)EmbedEditControl).Items; } } public OrpLookupEdit() : base() { EmbedEditControl = new GridEmbedEditControl(); } } } |
程式7是這兩個元件所用到的Design-Time程式碼列表。
程式7
using System; using System.ComponentModel; using System.ComponentModel.Design.Serialization; using System.Collections.Generic; using System.Reflection; using System.Text; using System.Globalization; using System.Windows.Forms; namespace LookupComboBox { sealed class ListItemConverter : ExpandableObjectConverter { public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) { if (destinationType == typeof(InstanceDescriptor)) { return true; } else { return base.CanConvertTo(context, destinationType); } } public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) { if (destinationType == null) { throw new Exception("destination type is null."); } if (destinationType == typeof(InstanceDescriptor) && value is ListItem) { ListItem item = (ListItem)value; ConstructorInfo constructorInfo = typeof(ListItem).GetConstructor(new Type[] { typeof(string), typeof(string) }); if (constructorInfo != null) { return new InstanceDescriptor(constructorInfo, new object[] { item.Text, item.Value }); } } return base.ConvertTo(context, culture, value, destinationType); } } sealed class LookupColumnItemConverter : ExpandableObjectConverter { public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) { if (destinationType == typeof(InstanceDescriptor)) { return true; } else { return base.CanConvertTo(context, destinationType); } } public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) { if (destinationType == null) { throw new Exception("destination type is null."); } if (destinationType == typeof(InstanceDescriptor) && value is LookupColumnItem) { LookupColumnItem item = (LookupColumnItem)value; ConstructorInfo constructorInfo = typeof(LookupColumnItem).GetConstructor(new Type[] { typeof(string), typeof(string), typeof(int) }); if (constructorInfo != null) return new InstanceDescriptor(constructorInfo, new object[] { item.Header, item.DisplayMember, item.Width }); } return base.ConvertTo(context, culture, value, destinationType); } } sealed class LookupColumnNameConverter : StringConverter { public override TypeConverter.StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) { GridEmbedEditControl control = (GridEmbedEditControl)((LookupColumnItems)((LookupColumnItem)context.Instance).Owner).Owner; PropertyDescriptorCollection cols = ListBindingHelper.GetListItemProperties(control.DataSource, control.DataMember, null); List<string> list = new List<string>(); foreach (PropertyDescriptor pd in cols) list.Add(pd.Name); StandardValuesCollection retCols = new StandardValuesCollection(list); return retCols; } public override bool GetStandardValuesSupported(ITypeDescriptorContext context) { return true; } } sealed class DataMemberConverter : StringConverter { public override TypeConverter.StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) { PropertyDescriptor pd = TypeDescriptor.GetProperties(context.Instance).Find("DataSource", true); if (pd != null) { List<string> list = new List<string>(); object dataSource = pd.GetValue(context.Instance); PropertyDescriptorCollection cols = ListBindingHelper.GetListItemProperties(dataSource); foreach (PropertyDescriptor pdItem in cols) list.Add(pdItem.Name); StandardValuesCollection retCols = new StandardValuesCollection(list); return retCols; } return base.GetStandardValues(context); } public override bool GetStandardValuesSupported(ITypeDescriptorContext context) { return true; } } } |
It’s Flexable?
無疑的,OrpEmbedControlEdit及OrpEmbedEditControl的搭配,將這種控件的延展性發揮到一個極致,當然!如果你問我,還有可以增進的空間嗎?我的答案會是有,只是目前尚未想到罷了。
Conclusion
在這兩篇文章中,我跳過了許多的基礎知識,不談Design-Time部份的處理,將重點放在了設計與問題的解決上,這使得這兩篇文章的易讀性降低不少,不過換來的是,你得到了兩個可以立即運用在現實專案上的控件。