詳解WinForm通用速選組件的構(gòu)建
對于WinForm組件,大家并不陌生。在這里我們將為大家講解WinForm通用速選組件的構(gòu)建,這也是通往WinForm開發(fā)的必經(jīng)之路。
用戶界面中,需要用戶進(jìn)行多項(xiàng)選擇時(shí),我們通常會提供一組快速選擇(以下簡稱速選)按鈕:全選、反選、清空,以方便用戶操作。本文章將會構(gòu)建一個(gè)通用速選組件來簡化操作,使用之后,您不需要編寫任何代碼,只需設(shè)置兩個(gè)屬性便可讓一個(gè)控件擁有速選的功能。
WinForm中常見的幾種多選形式如下圖:
圖1
我們暫且將用于顯示選項(xiàng)的控件叫做“選項(xiàng)控件”,全選、反選、清空叫做“速選按鈕”。
四種形式采用了不同的控件用作選項(xiàng)控件、速選按鈕:
選項(xiàng)控件 | 速選按鈕 | |
形式一 | CheckedListBox | Label |
形式二 | CheckBox | LinkLabel |
形式三 | TreeView | Button |
形式四 | DataGridView | PictureBox |
實(shí)際使用中還可能會有其它形式的選項(xiàng)控件和速選按鈕,根據(jù)使用場合不同,選項(xiàng)控件和速選按鈕可以任意組合。這給我們編程帶來了麻煩,選項(xiàng)控件沒有統(tǒng)一的訪問接口,換一個(gè)選項(xiàng)控件就要編寫不同的選擇代碼。
想要將全選、反選、清空的選擇邏輯提取出來還真不容易。于是很多程序員就選擇了針對實(shí)際應(yīng)用的組合(如形式一,CheckedListBox + Label)進(jìn)行直接編碼。這樣導(dǎo)致大量相似的代碼充斥在項(xiàng)目之中,會帶來以下問題:
- 重復(fù)的編碼、調(diào)試、測試(主要是界面)工作;
- 一旦選項(xiàng)控件發(fā)生改變,就必須修改代碼,修改后還要測試;(可能性比較大,可能客戶不喜歡形式一,非要你修改成形式二)
這樣的直接編碼實(shí)際上已經(jīng)違反了DRY原則,我們應(yīng)該糾正。
面向?qū)ο笠笪覀兎庋b變化,我們這里不變的是速選的邏輯,變化的是選項(xiàng)控件和速選按鈕,變化是兩個(gè)方向的,有點(diǎn)接近橋模式的應(yīng)用場景了。不過我對設(shè)計(jì)模式了解不深,不敢冒用,而且感覺這里速選按鈕的變化是比較簡單的,至少它們都有一個(gè)Click事件,也可以認(rèn)為是不變的。
#T#
我們采用另外一種途徑,.Net其實(shí)已經(jīng)給我們提供了一種擴(kuò)展控件功能的方式(也正體現(xiàn)了面向?qū)ο蟮腛CP原則),可以讓我們給控件賦予額外的功能。我們來看一個(gè)重要的接口:IExtenderProvider 接口,位于System.ComponentModel命名空間下。實(shí)現(xiàn)了這個(gè)接口的組件有ToolTip、ErrorProvider等,ToolTip、ErrorProvider這兩個(gè)組件可以向其它組件(控件是組件的一種)提供額外的功能,ToolStip能讓其它控件在用戶鼠標(biāo)懸停時(shí)彈出一個(gè)小框顯示一些提示信息,ErrorProvider則能讓控件顯式錯(cuò)誤信息。
我們要做的就是創(chuàng)建一個(gè)新的組件,實(shí)現(xiàn)IExtenderProvider接口,向控件(Button、Label等)提供速選的功能。這個(gè)組件我已經(jīng)完成了,名字叫FastSelect,先不考慮實(shí)現(xiàn)原理、如何實(shí)現(xiàn),我們先看下如何使用吧:
FastSelect是一個(gè)組件,會自動顯示在工具箱中,將其拖入窗體,將會顯示在設(shè)計(jì)器下方。
圖2
選中全選按鈕(label1),屬性顯示窗口如下圖:
圖3
分組中出現(xiàn)了一個(gè)新的分組:速選,其中有如上圖兩個(gè)屬性,***個(gè)屬性用于選擇選項(xiàng)控件,第二個(gè)屬性用于確定選擇方式(全選還是清空)。設(shè)置兩個(gè)屬性的值如上圖,完成后label1就可以全選分類中的所有選項(xiàng)了。
簡單說來,只需要向窗體置入一個(gè)控件(Button、Label、LinkLabel、Picture),簡單設(shè)置兩個(gè)屬性,這個(gè)控件就自動具有了速選功能,不需要任何代碼。當(dāng)然得借助FastSelect組件。
這么神奇,是如何實(shí)現(xiàn)的呢?要從IExtenderProvider接口說起,這個(gè)接口可以向其他組件提供屬性,如上圖中的兩個(gè)屬性。有這里有兩點(diǎn)要說明一下:
-
向其它組件提供屬性,這個(gè)可以限定,比如僅只向Button提供。
-
提供屬性并不是真正給其他組件加上新的屬性,只是在WinForm設(shè)計(jì)時(shí),在PropertyGrid中顯示額外屬性(后面會詳說)。
我們來看下FastSelect是如何實(shí)現(xiàn)的:
- public enum SelectionType
- {
- 清空,
- 反選,
- 全選,
- }
- [ProvideProperty("SelectionSource", typeof(Control))]
- [ProvideProperty("SelectionType", typeof(Control))]
- public partial class FastSelect : Component, IExtenderProvider
- {
- public bool CanExtend(object extendee)
- {
- if (extendee == null) return false;
- if (extendee is Button || extendee is Label || extendee is PictureBox) return true;
- return false;
- }
- [Category("速選"), Description("速選源控件"), Localizable(true)]
- public Control GetSelectionSource(Control control)
- {
- ...
- }
- public void SetSelectionSource(Control control, Control selectionSource)
- {
- ...
- }
- [Category("速選"), Description("速選方式"), DefaultValue(SelectionType.清空), Localizable(true)]
- public SelectionType GetSelectionType(Control control)
- {
- }
- public void SetSelectionType(Control control, SelectionType selectionType)
- {
- ...
- }
- ...
- }
SelectionType枚舉不必多說,我們來看FastSelect,它繼承至Component,實(shí)現(xiàn)了IExtenderProvider接口。
CanExtend是IExtenderProvider接口的***成員,它用來標(biāo)識可以給那些組件進(jìn)行擴(kuò)展,上面的代碼中我們限定了只可以給Button、Label、PictureBox進(jìn)行擴(kuò)展,也就是說其它類型的控件在屬性窗口中是看不到速選屬性的。CanExtend中沒有LinkLabel,是因?yàn)長inkLabel是Label的子類,Label有的它也會自動擁有。
Get(Set) SelectionSource、Get(Set)SelectionType有點(diǎn)類似屬性的get/set吧,只不過多了一個(gè)參數(shù)(***個(gè)參數(shù)control),傳入這個(gè)參數(shù)的是要進(jìn)行屬性擴(kuò)展的控件,還是讓我們看一下WinForm生成的代碼吧(在Form1.generated.cs中)
- this.fastSelect.SetSelectionSource(this.label1, this.categoriesListBox);
- this.fastSelect.SetSelectionType(this.label1, SelectionType.全選);
對照圖三,應(yīng)該明白“提供屬性”的真正含義了吧。
我們再來看是如何實(shí)現(xiàn)選擇的,先看代碼片段:
- public partial class FastSelect : Component, IExtenderProvider
- {
- private Dictionary
sourceControlsDict; - private Dictionary
typeDict; - public void SetSelectionSource(Control control, Control selectionSource)
- {
- sourceControlsDict.Add(control, selectionSource);
- control.Click += new EventHandler(control_Click);
- }
- public void SetSelectionType(Control control, SelectionType selectionType)
- {
- typeDict.Add(control, selectionType);
- }
- void control_Click(object sender, EventArgs e)
- {
- Control control = sender as Control;
- Control selectionSource = sourceControlsDict[control];
- SelectionType selectType = typeDict[control];
- if (selectionSource is DataGridView)
- {
- DataGridView dataGridView = selectionSource as DataGridView;
- foreach (DataGridViewRow row in dataGridView.Rows)
- row.Selected = ChangeSelected(row.Selected, selectType);
- }
- }
- private bool ChangeSelected(bool isSelected, SelectionType type)
- {
- if (type == SelectionType.清空) return false;
- else if (type == SelectionType.全選) return true;
- else if (type == SelectionType.反選) return !isSelected;
- else throw new NotImplementedException();
- }
- }
一個(gè)窗體上只需要一個(gè)FastSelect組件,但它要為其它多個(gè)組件提供屬性,我們這里使用Dictionary保存這些屬性。
設(shè)置控件SelectionSource屬性時(shí),其實(shí)是調(diào)用了SetSelectionSource方法,其中我們?yōu)榭丶粤薈lick事件,control_Click中根據(jù)不同的選項(xiàng)控件的類型(DataGridView、CheckedListBox等)和選擇類型進(jìn)行相應(yīng)的處理。這樣應(yīng)該明白了吧。
代碼比較長,就不發(fā)在文章中了,此處下載。(大部分時(shí)間花在文章上了,代碼沒太測試,可能存在問題,如發(fā)現(xiàn)請告知,謝謝了)。
原文標(biāo)題:構(gòu)建WinForm 通用速選(全選、反選、清空)組件
鏈接:http://www.cnblogs.com/ldp615/archive/2009/11/29/WinForm_FastSelect_Component.html