Designing and Implement Lookup Control for Windows Forms

摘要: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唯一會遭遇的技術困難是,FormWindows Forms架構中屬於容器型控件,每個Form都是單獨的個體,而Lookup Control所拉出的Form,必須受控於Lookup Control所在的Form,也就是當Lookup Control所在的Form移動時,這個拉下的Form也要跟著移動,這個問題有兩種解法,一種是MDI介面,不過此種方法雖可達到目的,但卻會引發其它的問題,就控件角度來說,我們不應該要求放Lookup ControlForm一定要是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);
        }
    }
如你所見,這是一個抽象類別,其中定義了InitializeControlParseValueGetInputValueClientFormClosed等函式,當OrpCustomEmbedControlEdit啟動下拉動作時,會建立一個Form, 然後呼叫InitializeControl函式,OrpEmbedEditControl必須在此將欲顯示於該下拉視窗中的控件填入,接著 ParseValue函式會被呼叫,此處必須依據傳入的值,調整視窗的內容,讓使用者可以看到原本所選取的值,然後必須處理選取資料的動作,當使用者選取 資料後,下拉視窗會被關閉,此時GetInputValue函式會被呼叫,其必須傳回使用者所選取的值,最後ClientFormClosed函式會被呼 叫,此處可以進行視窗關閉後的後續工作,整個流程圖示如圖3。
圖3
Implement
 
 完成了設計圖後,實作就不難了,OrpCustomEmbedControlEdit的工作在於建立下拉視窗,然後呼叫EmbedEditControl元件來填入內容物,這裡會遭遇到一個實作上的困擾,就是何時關閉視窗?這有幾種情況,一是使用者在拉下視窗後,又按下了下拉按鈕,此時自然得關閉視窗,這是Cancel模式,使用者選取的值不會填回OrpCustomEmbedControlEdit中。二是使用者於拉下視窗後,將焦點移到其它控件上,此時一樣視為Cancel模式,關閉視窗。三是使用者調整了含有OrpCustomEmbedControlEdit控件Form的大小,或是於其上點選了滑鼠,這一樣視為Cacnel模式。程式2OrpCustomEmbedControlEdit的原始碼列表,讀者可於其中看到處理視窗何時開啟、何時關閉的程式碼。
程式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這個設計的真正意含?是的!可動態切換的下拉視窗內容,可以讓設計師只用一個控件,應對不同的情況。程式4ListEmbedEditControl元件的原始碼。
程式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();
        }
    }
}
 
關於ListItemsDesignerSerializationVisibility及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?
 
 無疑的,OrpEmbedControlEditOrpEmbedEditControl的搭配,將這種控件的延展性發揮到一個極致,當然!如果你問我,還有可以增進的空間嗎?我的答案會是有,只是目前尚未想到罷了。
 
Conclusion
 
 在這兩篇文章中,我跳過了許多的基礎知識,不談Design-Time部份的處理,將重點放在了設計與問題的解決上,這使得這兩篇文章的易讀性降低不少,不過換來的是,你得到了兩個可以立即運用在現實專案上的控件。