在C#窗體應用程序開發中,窗體也是類,按照正常的先后順序來說,應該先介紹下接口和類再應該介紹窗體的,但是此處我會根據最常用和很容易忽略的地方來講解編程開發,更多的是提供一些自我思考的思路。
我們一開始學習的時候,都是從控制台程序學習,后來就想要做界面,自然而然的就接觸到了winform程序,然后使用了非常簡單,操作流程的方式將控件拖到了窗體中,然后點擊控件,開始修改一些屬性參數,稍微多使用幾次后,就會非常的嫻熟,我剛開始接觸時也不會去深究背后的原理技術是什么。上述的一系列操作關聯了非常多的技術細節,有許多的細節都是比較高級的主題,在初學者中確實不應該提及,但此處為了講明白這些原理卻又不得不提專業名詞,我相信只要是我們真的想學習編程,就一定想知道它為什么就能實現這些功能。
我們還是一個最簡單的winform窗體程序作為切入點,新建一個窗體程序后:
我們拋開設計器不談(設計器中其實沒有代碼支持,只是IDE提供的一個可視化操作效果),一個窗體程序,就是一個類,只不過在兩個地方定義了,第一部分是我們在代碼開發中經常碰到的,幾乎所有的代碼都在這里開發完成。
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.Linq; 7 using System.Text; 8 using System.Threading.Tasks; 9 using System.Windows.Forms; 10 11 namespace WindowsFormsApp1 12 { 13 public partial class Form1 : Form 14 { 15 public Form1() 16 { 17 InitializeComponent(); 18 } 19 20 } 21 }
第二部分的代碼藏的比較深,在Form1.Designer.cs中,至於資源存儲不再本次的討論范圍內。
1 namespace WindowsFormsApp1 2 { 3 partial class Form1 4 { 5 /// <summary> 6 /// 必需的設計器變量。 7 /// </summary> 8 private System.ComponentModel.IContainer components = null; 9 10 /// <summary> 11 /// 清理所有正在使用的資源。 12 /// </summary> 13 /// <param name="disposing">如果應釋放托管資源,為 true;否則為 false。</param> 14 protected override void Dispose(bool disposing) 15 { 16 if (disposing && (components != null)) 17 { 18 components.Dispose(); 19 } 20 base.Dispose(disposing); 21 } 22 23 #region Windows 窗體設計器生成的代碼 24 25 /// <summary> 26 /// 設計器支持所需的方法 - 不要修改 27 /// 使用代碼編輯器修改此方法的內容。 28 /// </summary> 29 private void InitializeComponent() 30 { 31 this.SuspendLayout(); 32 // 33 // Form1 34 // 35 this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); 36 this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 37 this.ClientSize = new System.Drawing.Size(528, 375); 38 this.Name = "Form1"; 39 this.Text = "Form1"; 40 this.ResumeLayout(false); 41 42 } 43 44 #endregion 45 } 46 }
我們可以清楚的看到第一部分的構造方法中調用了InitializeComponent()方法,該方法的源代碼就在第二個文件中,我們來看看源代碼中都寫了什么,掛起布局,設置放大縮小維度,模式,窗口大小,窗體名稱,顯示文本,恢復布局。OK,看到這里,我相信各位看官都可以嘗試着修改修改參數,看看會不會有什么變化,比如修改下窗體名稱,大小等等。再回到設計器界面就可以看到已經修改完成了,接下來就是重頭戲了,我們嘗試着在窗體上添加一個按鈕,就是拖控件的方式,拖上去后看看代碼會不會變化:
就是這樣的效果,先不要去追加按鈕事件,或是調整按鈕大小,我們來看看form1類的代碼會不會發生變化,我們發現Form1.cs中的代碼沒有任何改變,那么我們可以肯定另一個文件中發生了代碼變化:
1 namespace WindowsFormsApp1 2 { 3 partial class Form1 4 { 5 /// <summary> 6 /// 必需的設計器變量。 7 /// </summary> 8 private System.ComponentModel.IContainer components = null; 9 10 /// <summary> 11 /// 清理所有正在使用的資源。 12 /// </summary> 13 /// <param name="disposing">如果應釋放托管資源,為 true;否則為 false。</param> 14 protected override void Dispose(bool disposing) 15 { 16 if (disposing && (components != null)) 17 { 18 components.Dispose(); 19 } 20 base.Dispose(disposing); 21 } 22 23 #region Windows 窗體設計器生成的代碼 24 25 /// <summary> 26 /// 設計器支持所需的方法 - 不要修改 27 /// 使用代碼編輯器修改此方法的內容。 28 /// </summary> 29 private void InitializeComponent() 30 { 31 this.button1 = new System.Windows.Forms.Button(); 32 this.SuspendLayout(); 33 // 34 // button1 35 // 36 this.button1.Location = new System.Drawing.Point(238, 42); 37 this.button1.Name = "button1"; 38 this.button1.Size = new System.Drawing.Size(75, 23); 39 this.button1.TabIndex = 0; 40 this.button1.Text = "button1"; 41 this.button1.UseVisualStyleBackColor = true; 42 // 43 // Form1 44 // 45 this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); 46 this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 47 this.ClientSize = new System.Drawing.Size(528, 375); 48 this.Controls.Add(this.button1); 49 this.Name = "Form1"; 50 this.Text = "FormMy"; 51 this.ResumeLayout(false); 52 53 } 54 55 #endregion 56 57 private System.Windows.Forms.Button button1; 58 } 59 }
我們看到除了button1的聲明在form1類的下面,按鈕的實例化和屬性設置全部都是在方法InitializeComponent()中完成的,我們也可以嘗試着改改this.button1.Text = "測試按鈕";再回到界面設計器,發現同步更改了,如果我們在form1.cs中的任何的方法中更改button1的text,發現並沒有更改,所以我們可以得出一個結論,InitializeComponent()方法在設計器支持中非常的關鍵,中間的所有代碼將會影響設計器中的布局顯示。
說完上述的內容后,您可以按F11鍵,進行分步調試,直到應用程序退出為止,這樣就對整個程序執行的過程非常清晰,一個類被實例化以后,里面的變量一定是最先執行的。
- Program.cs文件中的Main方法最先執行
- 實例化窗口,配置窗口字段
- 執行構造方法,加載所有的控件資源
- 顯示
- 關閉前執行dispose方法
- 退出Main方法。
值得注意的是,窗體有四個常用的事件,關聯事件后如下:
1 private void Form1_Load(object sender, EventArgs e) 2 { 3 MessageBox.Show("Load"); 4 } 5 6 private void Form1_Shown(object sender, EventArgs e) 7 { 8 MessageBox.Show("Show"); 9 } 10 11 private void Form1_FormClosing(object sender, FormClosingEventArgs e) 12 { 13 MessageBox.Show("Closing"); 14 } 15 16 private void Form1_FormClosed(object sender, FormClosedEventArgs e) 17 { 18 MessageBox.Show("Closed"); 19 }
可以將一些初始化的代碼放入到Load方法和Show方法中,區別就在於Load是在窗體顯示前執行的,這時候窗體已經實例化了,並且加載了控件,而show就是在窗體已經可以顯示時候執行的。如果在show方法中執行一些比較耗時的代碼的話,就會明顯的感覺窗體顯示出來的時候卡頓了一會,這種情況就放到load方法中比較合適。如果有退出窗體的確認或是密碼驗證的需求,可以在formClosing中編寫。下面舉個例子說明退出確認:
1 private void Form1_FormClosing(object sender, FormClosingEventArgs e) 2 { 3 if (MessageBox.Show("是否真的退出窗口?", "退出確認", MessageBoxButtons.YesNo) == DialogResult.No) 4 { 5 e.Cancel = true; 6 } 7 //MessageBox.Show("Closing"); 8 }
動態控件
有時候我們希望創建一個動態的按鈕出來,就是原先它不存在的,突然有了,還可以點擊(按照道理上我們可以使用一個按鈕的Visible屬性來控制顯示和消失可以達到相似的目的),但是我們仍然需要知道控件是怎么創造出來的,比如上述項目按鈕點擊一下在旁邊生成一個新的按鈕,具體怎么生成,我們可以參照button1是怎么生成的,所以如下代碼:
1 private void button1_Click(object sender, EventArgs e) 2 { 3 Button button = new Button(); 4 button.Location = new System.Drawing.Point(328, 42); 5 button.Name = "button2"; 6 button.Size = new System.Drawing.Size(75, 23); 7 button.TabIndex = 0; 8 button.Text = "button2"; 9 button.UseVisualStyleBackColor = true; 10 11 this.Controls.Add(button); 12 }
點擊之后的效果如下:
如果我們希望按鈕2可以支持點擊事件,則稍微修改下代碼即可:
1 private void button1_Click(object sender, EventArgs e) 2 { 3 Button button = new Button(); 4 button.Location = new System.Drawing.Point(328, 42); 5 button.Name = "button2"; 6 button.Size = new System.Drawing.Size(75, 23); 7 button.TabIndex = 0; 8 button.Text = "button2"; 9 button.UseVisualStyleBackColor = true; 10 button.Click += Button_Click; 11 12 this.Controls.Add(button); 13 } 14 15 private void Button_Click(object sender, EventArgs e) 16 { 17 MessageBox.Show("你點擊了button2"); 18 }
屬性說明
我們很習慣於選擇控件后設置屬性,就像下面這張圖片一樣:
設置光標也好,文本也罷,不知道細心的你有沒有發現,我們為什么能這樣設置,這個IDE的屬性設計器為什么能知道控件的屬性以及需要設置的數據內容,這一切的一切都被深深的藏在了底層,VS首先加載動態鏈接庫,找到控件內容,然后通過反射來獲取到屬性名稱,屬性類別,等你在上面窗口設置好新的值后,就生成相應的代碼來覆蓋原有的值。至於所有屬性的解釋,都是特性來完成的,以后在講解自定義控件的時候,再來着重說明。