C#中的自定義控制元件中的屬性、事件及一些相關特性的總結
今天學習了下C#使用者控制元件開發新增自定義屬性的事件,主要參考了MSDN,總結並實驗了一些用於開發自定義屬性和事件的特性(Attribute)。
在這裡先說一下我的環境:
作業系統:Windows7旗艦版(Service Pack 1)
VS版本:Microsoft Visual Studio Ultimate 2012,版本 11.0.50727.1 RTMREL
.NET Framework版本:4.5.50938
C#版本:Visual C# 2012
一、準備工作
1、建立一個C#窗體應用程式,主窗體起名為FormMain,向解決方案中再加入一個使用者控制元件,起名為TestUserControl
2、在TestUserControl中放一個按鈕,取名為btnTest
3、控制元件做好後,會出現在【工具箱】內
4、將控制元件拖拽到一個窗體(Form)上就可以使用了,取名testUserControl1。這個名字是VS預設取的,即首字母小寫,最後補上數字作為序號。
二、新增自定義屬性
在TestUserControl類中,新增下面的程式碼:
/// <summary> /// 按鈕名稱 /// </summary> public string ButtonName { get { //TODO return btnTest.Text; } set { //TODO btnTest.Text = value; } }
程式碼新增完畢後,在FormMain上加入的testUserControl1的屬性中,就會出現BtnName了
三、新增自定義事件
在TestUserControl類中,新增下面的程式碼:
/// <summary> /// 事件 /// </summary> public event EventHandler BtnTestClick; /// <summary> /// 測試按鈕 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnTest_Click(object sender, EventArgs e) { if (BtnTestClick != null) { //TODO BtnTestClick(sender, e); } }
程式碼新增完畢後,在FormMain上加入的testUserControl1的事件中,就會出現BtnTestClick了
在FormMain的程式碼中實現這個函式
private void testUserControl1_BtnTestClick(object sender, EventArgs e)
{
MessageBox.Show(sender.ToString() + "\r\n" + e.ToString());
}
這時執行程式,點選控制元件testUserControl1內的按鈕btnTest,就會有下面的效果:
四、幾個特性(Attribute)
1)DefaultEvent和DefaultProperty:指定自定義控制元件的預設事件和預設屬性
DefaultEventAttribute(MSDN)可以用來指定元件的預設事件,如在TestUserControl類上面加入程式碼
[DefaultEvent("BtnTestClick")]
那在Form編輯介面,雙擊控制元件testUserControl1就會自動進入testUserControl1_BtnTestClick事件。
這裡再說明一下,C#中的System.Windows.Forms.Control類程式碼如下:
[ClassInterface(ClassInterfaceType.AutoDispatch)]
[ComVisible(true)]
[DefaultEvent("Click")]
[DefaultProperty("Text")]
[Designer("System.Windows.Forms.Design.ControlDesigner, System.Design, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]
[DesignerSerializer("System.Windows.Forms.Design.ControlCodeDomSerializer, System.Design, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", "System.ComponentModel.Design.Serialization.CodeDomSerializer, System.Design, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]
[ToolboxItemFilter("System.Windows.Forms")]
public class Control : Component, IDropTarget, ISynchronizeInvoke, IWin32Window, IBindableComponent, IComponent, IDisposable
{ /* ... */ }
這裡可以看到DefaultEvent的值為“Click”,這也就是為什麼拖入Form的按鈕(Button),在雙擊後會進入它的Click事件:
private void button1_Click(object sender, EventArgs e)
對於不希望以Click事件作為預設事件的控制元件來說,要手動指定該控制元件的DefaultEvent特性,如複選框(CheckBox)的宣告:
[ClassInterface(ClassInterfaceType.AutoDispatch)]
[ComVisible(true)]
[DefaultBindingProperty("CheckState")]
[DefaultEvent("CheckedChanged")]
[DefaultProperty("Checked")]
[ToolboxItem("System.Windows.Forms.Design.AutoSizeToolboxItem,System.Design, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]
public class CheckBox : ButtonBase
{ /* ... */ }
這裡的DefaultEvent被寫上了“CheckedChange”,因此在Form的編輯介面,雙擊複選框時預設進入的編輯事件為
private void checkBox1_CheckedChanged(object sender, EventArgs e)
自定義的控制元件(直接繼承自UserControl),如果不新增這個屬性,在編輯介面雙擊後進入的事件是Load事件。
類似的特性還有DefaultProperty,DefaultPropertyAttribute(MSDN)可以被用來指定元件的預設屬性。指定預設屬性後,當用戶在Form裡單擊這個控制元件時,將在屬性瀏覽視窗中自動選定該屬性:
[DefaultProperty("BtnName")]
2)Browsable:設定控制元件某一屬性或事件是否出現在“屬性”視窗中
BrowsableAttribute(MSDN)指定某一屬性或事件是否應在“屬性”視窗中顯示,如在屬性BtnName上新增程式碼:
[Browsable(false)]
則控制元件testUserControl1的屬性介面就不會出現BtnName的設定了,下圖紅線部分為之前BtnName所在的位置
如果某屬性或事件沒有新增Browsable特性,那麼該屬性或事件也可以在“屬性”視窗中看到。這裡還要說明以下,Browsable只能決定某屬性或事件在“屬性”視窗內的可見性,Browsable被置為false的屬性和事件,仍可以在編輯器中通過程式碼中使用。
3)Description:指定控制元件某一屬性或事件出現在“屬性”視窗中的說明文字
DescriptionAttribute(MSDN)用於指定控制元件的某一屬性或事件出現在“屬性”視窗中的說明文字
如在BtnName上新增下面程式碼:
[Description("設定按鈕上顯示的文字")]
也可以帶上Browsable特性一起使用:
[Browsable(true)]
[Description("設定按鈕上顯示的文字")]
或寫在一對方括號裡,用逗號隔開:
[Browsable(true), Description("設定按鈕上顯示的文字")]
在“屬性”介面中看到的說明文字,效果如下:
4)EditorBrowsable:指定某一屬性或方法在編輯器中可見
EditorBrowsableAttribute(MSDN)指定某個屬性或方法在編輯器中可以檢視。
EditorBrowsableAttribute的建構函式如下:
public EditorBrowsableAttribute(EditorBrowsableState state);
其中,EditorBrowsableState是一個列舉(enum),這個列舉共有三個值,分別是Always、Never和Advanced
Always:該屬性或方法在編輯器中始終是可瀏覽的
Never:該屬性或方法始終不能在編輯器中瀏覽
Advanced:該屬性或方法是隻有高階使用者才可以看到的功能。 編輯器可以顯示或隱藏這些屬性
前面兩個都好理解,第三個Advanced著實會讓人一頭霧水(什麼才叫“高階使用者”?)。後來查了一些資料,才知道對於高階成員的可見性,可以在“工具”選單下的“選項”中進行配置。
(在這裡感謝大神在social.msdn.microsoft.com上的 解答 )
如果勾選了“隱藏高階成員”,那麼用程式碼“[EditorBrowsable(EditorBrowsableState.Advanced)]”標記的屬性,將不能在IDE中自動顯示。但這也僅僅是不自動顯示而已,如果在程式碼中真的呼叫了不可見的屬性,編譯不會報錯,執行也不會有問題。
如下圖:BtnName被標記為“EditorBrowsableState.Never”,因此這個屬性不會出現在VS的智慧提示(學名叫IntelliSense)中,但如果寫到程式碼裡,卻沒有問題。
需要注意的是,這種隱藏只有在該控制元件程式碼為當前解決方案不可見時有效,也就是說,如果這個控制元件的實現程式碼就在你的解決方案內,EditorBrowsable並不能保證使用者看不見這個屬性。但如果這個控制元件時被放在一個dll檔案中新增引用到當前的解決方案中,EditorBrowsable特性才能按其文字描述中說明的那樣起作用。
5)DesignerSerializationVisibility:程式碼生成器生成元件相關程式碼的方式
DesignerSerializationVisibilityAttribute(MSDN)用於指定在設計時序列化元件上的屬性時所使用的永續性型別。
引數為DesignerSerializationVisibility型別的列舉:
Hidden:程式碼生成器不生成物件的程式碼
Visible:程式碼生成器生成物件的程式碼
Content:程式碼生成器產生物件內容的程式碼,而不是物件本身的程式碼
這個說法一眼看上去並不易理解,因此我決定還是用兩個具體例子說明一下:
1、Hidden與Visible、Content的不同
還是以我們上面的BtnName屬性為例,引數為【DesignerSerializationVisibility.Hidden】的情況
[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden)]
public string BtnName
{
get
{
return btnTest.Text;
}
set
{
btnTest.Text = value;
}
}
將控制元件拖入FormMain的窗體設計器中,可用在檔案FormMain.Designer.cs中看到:
/// <summary>
/// 設計器支援所需的方法 - 不要
/// 使用程式碼編輯器修改此方法的內容。
/// </summary>
private void InitializeComponent()
{
this.testUserControl1 = new ControlTest.TestUserControl();
this.SuspendLayout();
//
// testUserControl1
//
this.testUserControl1.Location = new System.Drawing.Point(33, 46);
this.testUserControl1.Name = "testUserControl1";
this.testUserControl1.Size = new System.Drawing.Size(134, 77);
this.testUserControl1.TabIndex = 0;
// ...
}
將BtnName上方的特性DesignerSerializationVisibilityAttribute的引數改為【DesignerSerializationVisibility.Visible】或【DesignerSerializationVisibility.Content】後,函式InitializeComponent()中的程式碼會有不同:
/// <summary>
/// 設計器支援所需的方法 - 不要
/// 使用程式碼編輯器修改此方法的內容。
/// </summary>
private void InitializeComponent()
{
this.testUserControl1 = new ControlTest.TestUserControl();
this.SuspendLayout();
//
// testUserControl1
//
this.testUserControl1.BtnName = "button1";
this.testUserControl1.Location = new System.Drawing.Point(36, 32);
this.testUserControl1.Name = "testUserControl1";
this.testUserControl1.Size = new System.Drawing.Size(134, 77);
this.testUserControl1.TabIndex = 0;
// ...
}
可用看出,區別就在下面這行程式碼:
this.testUserControl1.BtnName = "button1";
使用了Hidden就沒有,使用了Visible就會有(使用了Content也會有)
使用了Hidden後,在“屬性”介面中,無論怎麼修改BtnName屬性的值,編譯時編譯器都不會理睬這個值,而是使用預設值(這個例子裡面就是button1)。使用了Hidden後,即使在FormMain.Designer.cs裡手動把上面那行賦值的程式碼加上,這行程式碼在程式重新編譯後還是會消失。
2、Visible與Content的不同
Content被用在可以序列化的集合,例如System.Windows.Forms.DataGridView類(資料表格)
//
// 摘要:
// 獲取一個包含控制元件中所有列的集合。
//
// 返回結果:
// 一個 System.Windows.Forms.DataGridViewColumnCollection,包含 System.Windows.Forms.DataGridView
// 控制元件中的所有列。
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[Editor("System.Windows.Forms.Design.DataGridViewColumnCollectionEditor, System.Design, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))]
[MergableProperty(false)]
public DataGridViewColumnCollection Columns { get; }
IDE只是生成這些屬性中包含元件的程式碼,而不會生成屬性本身的程式碼。在使用IDE新增各個DataGridViewTextBoxColumn時,各個DataGridViewTextBoxColumn的程式碼會被放在FormMain.Designer.cs檔案中,而有關Columns屬性本身只會在在函式InitializeComponent()中生成這樣一段程式碼:
this.dataGridView1.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
this.Column1,
this.Column2,
this.Column3});
6)其他特性
其他的特性還有許多(如Localizable被用於指定屬性是否可本地化、DefaultValue用於為屬性指定另一個“預設值”等),如只是初步瞭解可以去檢視VS從程式集 System.Windows.Forms.dll中反射出的各控制元件、控制元件屬性、控制元件事件的宣告和摘要(就是宣告上面的綠字),更詳細的描述可以去參考MSDN。
END