Designing and Implement Lookup Control for Windows Forms
文/黃忠成
What’s Lookup Control
前篇所開發的
OrpButtonEdit
控件,雖然已經達到了初步的需求,但使用這個控件,設計師仍然必須自行設計開出的查詢視窗、處理選取的資料、回填至
ButtonEdit
控件中等課題,然而這些動作都是可規格化的,本文中所開發的
Lookup Control
將針對此問題,做出更便利的選取資料介面。事實上,
Lookup Control
在很早期的商用應用程式就已出現,她是一個類似
ComboBox
的控件,只是拉出的視窗不僅僅顯示單欄資料,而是顯示出一個
Grid
,讓使用者可以看到一整筆資料,而非僅僅單一欄位,見圖
1
。
圖
1
![](https://p-blog.csdn.net/images/p_blog_csdn_net/Code6421/020e3df710f74357a545a0f219ee1a2b.png)
設計這樣的控件,有兩個不可缺的關鍵控件,一是
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
![](https://p-blog.csdn.net/images/p_blog_csdn_net/Code6421/affeb1d78bea48609847e30add80892d.png)
程式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
![](https://p-blog.csdn.net/images/p_blog_csdn_net/Code6421/60afced1c48a459287c3449c13c5b100.png)
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
![](https://p-blog.csdn.net/images/p_blog_csdn_net/Code6421/689b9e71aed244b982bbd828f4d1d49b.png)
聰明的你,是否看出
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是執行畫面。
![](https://p-blog.csdn.net/images/p_blog_csdn_net/Code6421/98c24d74a825452cb5ce7a9e099173ba.png)
你也可以使用前面所開發的
OrpEmbedControlEdit
控件,而非
OrpComboBox(
這是一個整合了
OrpEmbedControlEdit
控件及
ListEmbedEditControl
元件的控件
)
,圖
6
是其設計時期畫面。
圖
6
![](https://p-blog.csdn.net/images/p_blog_csdn_net/Code6421/fc06a105cfc6478ca5e8345005532a46.png)
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
部份的處理,將重點放在了設計與問題的解決上,這使得這兩篇文章的易讀性降低不少,不過換來的是,你得到了兩個可以立即運用在現實專案上的控件。
v