在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首先加载动态链接库,找到控件内容,然后通过反射来获取到属性名称,属性类别,等你在上面窗口设置好新的值后,就生成相应的代码来覆盖原有的值。至于所有属性的解释,都是特性来完成的,以后在讲解自定义控件的时候,再来着重说明。