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
Designing and Implement Lookup Control for Windows Forms_第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
Designing and Implement Lookup Control for Windows Forms_第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 啟動下拉動作時,會建立一個 For m ,然後呼叫InitializeControl函式,OrpEmbedEditControl必須在此將欲顯示於該下拉視窗中的控件填入,接著ParseValue函式會被呼叫,此處必須依據傳入的值,調整視窗的內容,讓使用者可以看到原本所選取的值,然後必須處理選取資料的動作,當使用者選取資料後,下拉視窗會被關閉,此時GetInputValue函式會被呼叫,其必須傳回使用者所選取的值,最後ClientFormClosed函式會被呼叫,此處可以進行視窗關閉後的後續工作,整個流程圖示如圖3。
圖3
Designing and Implement Lookup Control for Windows Forms_第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
Designing and Implement Lookup Control for Windows Forms_第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 DesignerSerializationVisibilit y 及TypeConverter部份,請參考拙著:『深入剖析 ASP.NET組件設計』一書,此處就不再贅述。ListEmbedEditControl元件的重點只有一個,那就是InitializeControl函式,此處建立了一個ListBox控件,並放入由OrpCustomEmbedControlEdit所傳入的Form中,剩下的動作就是如何與其互動罷了,圖5是執行畫面。
Designing and Implement Lookup Control for Windows Forms_第5张图片
你也可以使用前面所開發的 OrpEmbedControlEdit 控件,而非 OrpComboBox( 這是一個整合了 OrpEmbedControlEdit 控件及 ListEmbedEditControl 元件的控件 ) ,圖 6 是其設計時期畫面。
6
Designing and Implement Lookup Control for Windows Forms_第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 部份的處理,將重點放在了設計與問題的解決上,這使得這兩篇文章的易讀性降低不少,不過換來的是,你得到了兩個可以立即運用在現實專案上的控件。


Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1145846


你可能感兴趣的:(windows)