Saturday, February 17, 2007

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)) &amp;& !(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)) &amp;& !(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部份的處理,將重點放在了設計與問題的解決上,這使得這兩篇文章的易讀性降低不少,不過換來的是,你得到了兩個可以立即運用在現實專案上的控件。

No comments: