Designing and Implement Lookup Control for Windows Forms

<iframe align="top" marginwidth="0" marginheight="0" src="http://www.zealware.com/46860.html" frameborder="0" width="468" scrolling="no" height="60"></iframe>
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 : ListListItem>
{
public int FindValue(string value)
{
for (int i = 0; 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
{
分享到:
评论
happmaoo
  • 浏览: 1285498 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

你可能感兴趣的:(windows,UI,asp.net,asp)