轉自https://blog.csdn.net/qq_33712555/article/details/80940569
注意:控件的類和部分類(包括窗體生成類/窗體設計類)全部要加public來修飾,不然無法在工具箱里找到
控件庫,說白了,就是個類庫項目。不過這個類庫內定義了一系列自定義的控件。整個項目(類庫+調用項目)結構大概是這個樣
請忽略Class2
和ClassDiagram1.cd
,這是我之前一個博文
用到的項目,留下來也無關緊要。UserControl2.cs
就是我們的一個控件了。長這個樣
一個TextBox下面有一條線,線寬等於文本框的寬等於整個控件的寬。當然,這需要在cs代碼中做一些調整(設置屬性或是什么)。代碼長這個樣,僅供參考
private int width;
- 1
定義了一個字段,用來指示控件的寬度,private。然后定義了一個屬性,也是用來指示控件寬度,public。這樣在類外訪問控件寬度需通過屬性,而不是直接把字段暴露給外部。
[Description("控件寬度"), Category("自定義屬性")] public int selfWidth { get { return width; } set { this.width = value; this.Width = value; this.txtBox.Width = value; this.pictureBox.Width = value; } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
其中
[Description("控件寬度"), Category("自定義屬性")]
- 1
加上這行表明這個屬性代表了控件寬度
,后面的Category
是分組情況。這行的意義是用了這個控件之后,就能在當前窗體設計器上面方便地改動屬性值,就像這樣
還有最后一段代碼
private void UserControl1_SizeChanged(object sender, EventArgs e) { this.selfWidth = this.Width; }
- 1
- 2
- 3
- 4
- 5
盡管這個控件在主調窗體加載完之后大小就固定了,但是仍要增加控件尺寸改變事件。這樣做是為了保證控件在窗體設計階段拖拽能即時地調整自己相關的屬性,而不至於外殼改變了,而里面的東西卻還是老樣子。
最后在我們需要用到這個控件的項目中調用相關dll
就OK了。
寫控件庫一點都不難,只需要同樣的過程重復N遍再加上一點創意,必要時還需要用點別人的東西。
今天學習了下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