《叩响C#之门》接触窗体编程 基本知识 和 自动产生的部分代码分析(11月7日更新)
一、一些概念性知识
1、基本概念
1)在Windows中,具有图形用户界面(Graphics User Interface,GUI)的程序称为窗体程序,窗体程序由窗体(Form)和组件(Component)构成,每个应用程序都有一个主窗体,主窗体中分门别类地排列着各种组件。窗体也可以看成是一个放置组件的容器(Container),组件有些是可见的,有些组件是不可见的,可见的组件称为控件(Control)。
2)关键字partial允许把同一个类分别定义在多个源文件中,窗体编程中,把需要人工编辑的代码放在诸如Form1.cs此类的文件中,而自动生成、不需要人工修改的代码定义在另一个源文件中。自动生成的有两个源文件,一个是以resx为扩展名,如Form1.resx,它包括窗体中的所有资源(resource,前三个字母正好是扩展名resx的前三个字母),包含字符串、图标、位图等;另一个文件后缀为Designer.cs,如Form1.Designer.cs,主要包含了声明控件和初始化窗体等代码,在代码中,InitializeComponent()方法,即初始化组件方法,用来初始化窗体和窗体中的控件。
3)编写窗体程序相当于编写一个派生于Form类的新窗体类(Form类是在System.Windows.Forms命名空间中),它继承了Form类的所有外观特征和行为特征。所以当生成的时候,Form1.Designer.cs中的InitializeComponent()代码很少,因为所有的属性值都是Form类初始化时默认的值,只有如下代码:
this.components = new System.ComponentModel.Container(); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.Text = "Form1";
一旦在Form1中对继承的属性进行改变,则Form1.Designer.cs中的InitializeComponent()代码开始增加,当后面再添加控件后,内容将更加丰富起来。
4)在新建项目选择Windows应用程序时,输入的名称,将作为项目名称、程序集名称、默认命名空间名称,以及实际自动生成代码中所用的命名空间名称,如Form1.Designer.cs的代码都在一个命名空间中,这个命名空间就是上述的这个名称;需要人工编辑的代码Form1.cs中主程序代码所在的命名空间也是这个名称。该名称与解决方案名称没有关联。
5)当手工在Form1.Designer.cs代码中的InitializeComponent()修改Form1窗体的名称(this.Name=”修改的窗体名”),发现对程序没什么变化,设计器里窗体名称依然还是原来的名称,程序可以正常运行;如果在设计器里对窗体名进行修改,则Form1.Designer.cs中的上述对应位置自动改成新的名称,同时,Form1.Designer.cs和Form1.cs、Program.cs中的对应类名和构造函数都自动变为新的名称。
6)Program.cs中的代码也是在同一个命名空间中,并拥有程序的主入口Main()函数。在Main()函数中使用了System.Windows.Forms命名空间中的Application类,该类提供了一系列管理窗体程序的静态方法,如EnableVisualStyles()方法启用应用程序的可视样式,Exit()方法退出程序,Run()方法启动程序。其中在Run()的参数中是用new创建了一个Form1的对象,如Application.Run(new Form1()); 可见编写窗体程序实际上就是编写一个继承于Forms命名空间中的Form类的窗体类,然后通过Forms命名空间中的Application类的静态方法Run()去运行该类的一个实例。
2、添加控件中涉及的基本概念
1)各种控件实际上都是类,创建一个控件,如创建按钮,就相当于在窗体类中声明了一个Button类的对象(在Form1.Designer.cs中)。也就是在Form1类中添加了成员变量。
//创建按钮button1相当于在窗体类Form1中声明了一个Button类的对象button1,即生成了一个引用符,这个引用符变量是Form1类的私有成员变量。
private System.Windows.Forms.Button button1;
//然后在Form1类的InitialComponent()方法中,先对这个按钮控件对象引用符进行初始化,将实际的对象地址传给它们 this.button1 = new System.Windows.Forms.Button();
2)添加Button类的对象后,Form1.Designer.cs的InitialComponent()方法中需要熟悉的代码如下:
// Form1 // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);//获取或设置控件的设计尺寸 this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;//获取或设置控件的自动缩放模式 this.ClientSize = new System.Drawing.Size(292, 273);//获取或设置窗体工作区大小 this.Controls.Add(this.button1); /* * Controls是属于System.Windows.Forms命名空间中的Control类中的一个只读属性,这个属性是以Control类中的嵌套类ControlCollection为类型的属性,用于获取包含在控件内的控件的集合。 * Add是属于System.Windows.Forms命名空间中的Control类中的嵌套类ControlCollection中的一个虚方法,将指定的控件添加到控件集合中 */ this.Name = "Form1";//获取或设置控件的名称 this.Text = "Form1"; this.ResumeLayout(false);
3)无意间,对Controls和ControlCollection下的Add()方法研究中,发现C#中的一些有趣知识:
a、类是可以嵌套类的;
b、类可以定义为静态类,只不过这种类是拒绝被实现为对象的,只能放静态方法和字段及属性,不能有实例成员,但在静态类下继续嵌套的类不用定义为静态类,也不是必须拥有静态方法及属性、字段,静态类下嵌套的非静态类可以被实例化;
c、输出各对象的类型可以直接用对象,或者对象.GetType()或者对象.ToString(); 如果是静态类,则只能通过typeof(静态类名) 来得到对应的CTS类型名,嵌套的类输出的类型名很神奇。
d、嵌套类的作用:譬如A类嵌套了B类,一般用法:A类会定义一个具有B类型的属性C或方法D(),通过属性C或方法D()可以在程序中很好的操控B类中的方法,看似调用B类的方法时,没有针对B类实现实例,实际上不管C还是D(),因其类型是B,所以必须有返回值,该返回值就是B类的一个实例,因此再通过C或D()调用B类的方法也就顺理成章了。
随手试验的代码如下(随手的试验,代码比较乱,仔细看能明白在试验什么):
using System; using System.Collections.Generic; using System.Text; namespace Test { public class Test { public string str; public Test2 testTest = new Test2(); public Test2 Test2Pro { get { return testTest; } } public Test2 Test2Fun() { return testTest; } public void Function() { } public static class Test1 { static int i; public static void ppp() { Console.WriteLine("hello Test1"); } public class c { int i; public static void ppp() { Console.WriteLine("hello Test2"); } } } public class Test2 { public class Test22 { } public static void p1() { Console.WriteLine("hello Test2"); } public void p2() { Console.WriteLine("由Test类属性或方法调出来"); } } } class Program { static void Main(string[] args) { Test test = new Test(); Test.Test2 test2 = new Test.Test2(); test.Test2Pro.p2(); test.Test2Fun().p2(); test.testTest = new Test.Test2(); Test.Test1.c cVar = new Test.Test1.c(); Console.WriteLine(test); Console.WriteLine(test2); Console.WriteLine(test.testTest); Console.WriteLine(cVar); Console.WriteLine(typeof(Test.Test1)); } } }
4)在写注释时,发现注释之所以要放在要注释的代码前面,除了清楚以外,另外,如果放在后面,当程序自动在这段代码后面生成代码时,新生成的代码是插在原代码和注释之间的,也就是说如果注释放在代码后面,一旦程序自动在代码后生成新代码,注释就变成了新代码的注释了,容易出问题。
5)自动生成代码的小结
Form1.Designer.cs文件内
在窗体上添加控件,实际上就是在Form1类中添加成员变量,这个成员变量声明后,是在InitializeComponent()方法中进行了初始化,并进行空间变量所有属性的赋值,窗体的属性赋值也在该方法中进行。
Form1.cs文件内
定义了Form1类的构造函数,其实一般也就是调用InitializeComponent()函数,以实现控件的初始化,以及窗体、控件的属性赋值。
窗体、各控件的事件处理程序也定义在该文件中,需手工编写代码。
Program.cs文件内
主要是主程序入口,通过Forms命名空间中的Application类的静态方法Run()去运行窗体类Form1的一个实例。
3、System.Windows.Forms命名空间中有大量的类需要学习和掌握
譬如,MessageBox类 显示可包含文本、按钮和符号(通知并指示用户)的消息框。
最主要的方法就是 Show() 该方法有大量重载,最简单的参数就是一个字符串。
4、System.Diagnostics (诊断)命名空间提供特定的类,使您能够与系统进程、事件日志和性能计数器进行交互。
EventLog 组件提供在网络上写入事件日志、读取事件日志项以及创建和删除事件日志与事件源的功能。
Process 类提供下列功能:监视整个网络的系统进程以及启动和停止本地系统进程。
PerformanceCounter 类使您能够监视系统性能,而 PerformanceCounterCategory 类则提供新建自定义计数器和类别的方式。
重点针对Process类 提供对本地和远程进程的访问并使您能够启动和停止本地系统进程。
Process.Start(参数列表) 被重载
参数只有一个字符串,通过指定文档或应用程序文件的名称来启动进程资源,并将资源与新的 Process 组件关联。
public static Process Start (
string fileName
)
参数是两个字符串,通过指定应用程序的名称和一组命令行参数来启动一个进程资源,并将该资源与新的 Process 组件相关联。
public static Process Start (
string fileName,
string arguments
)
参数
fileName
要在该进程中运行的应用程序文件的名称。
arguments
启动该进程时传递的命令行参数。
返回值
与该进程关联的新的 Process 组件,或者如果没有启动进程资源(例如,如果重用了现有进程),则为 空引用
5、刚学的窗体代码都是在一个命名空间完成
现在做的都是单线程程序,因此想通过Button的Click事件再调用一个新的Application(窗体实例)时,会出现如下bug:
“在单个线程上开始另一个消息循环是无效操作,请改用 Form.ShowDialog。”
1)首先对Program.cs中的代码进一步尝试,特别是对Application类中的方法做简单的研究
a、当我再Program.cs文件内写下如下代码时
static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new Form1()); Form1 myForm = new Form1(); myForm.Text = "第二个窗体"; Application.Run(myForm); }
发现两个窗体不是同时打开,而是先打开第一个,当地一个关闭以后,第二个窗体才打开,说明Application.Run()方法在窗体为退出之前是不会流转到下一句代码的。
b、Application.Restart() 该语句一用(放在上述程序的最后一句),发现窗口不管怎么关,都会再次重新打开,两个窗体出现的顺序和上面的一样。
Restart()函数 关闭应用程序并立即启动新的实例。
c、Application.Run() 有三个重载,现在只能大概看明白参数为窗体实例的用法。
Application.Run(窗体实例) 在当前线程上开始运行标准应用程序消息循环,并使指定窗体可见。
d、Application.Exit()方法,通知所有消息泵必须终止,并且在处理了消息以后关闭所有应用程序窗口。
可以放在按钮的Click事件内,点击该button,就会退出程序。但如果有了Restart()语句后,及时Exit,也不能真正推出程序。
另外,ExitThread() 退出当前线程上的消息循环,并关闭该线程上的所有窗口。
e、Application.UserAppDataPath属性是字符串值,为 获取用户的应用程序数据的路径。
f、StartupPath属性 获取启动了应用程序的可执行文件的路径,不包括可执行文件的名称。
2)窗体的方法和属性
属性
a、AcceptButton 获取或设置当用户按 Enter 键时所单击的窗体上的按钮。
试了,但是发现不好使,不知哪里出了错误。
b、Visible 获取或设置一个值,该值指示是否显示该控件。其实不论窗体还是控件,该属性都通用。
c、BackColor 用于获取或设置窗体的背景色
d、TransparencyKey 获取或设置将表示窗体透明区域的颜色。 需和BackColor配合使用
this.BackColor = Color.Red; this.TransparencyKey = Color.Red;
将上述语句放到一个按钮的Click事件中,可以看到窗体变透明后的变化。
e、Form.ActiveForm 属性 只读属性
获取此应用程序的当前活动窗体。 可以使用此方法获得对当前活动窗体的引用,以在该窗体或其控件上执行操作。 如果应用程序是多文档界面 (MDI) 应用程序,请使用 ActiveMdiChild 属性获得当前活动的 MDI 子窗体。
public static Form ActiveForm { get; }
如: Form currentForm = Form.ActiveForm;
f、AutoScaleMode属性还没找到在哪里使用合适,但MSDN上很有用。
AutoSizeMode属性(获取或设置窗体自动调整自身大小的模式。)还是很有用的,两个值,分别为GrowAndShrink 控件根据它的内容增大或缩小,GrowOnly 控件可以根据其内容任意增大,但不会缩小至小于它的 Size 属性值。 但在使用前一定要将 AutoSize置为true。程序中已试验。
g、ModifierKeys 获取一个值,该值指示哪一个修改键(Shift、Ctrl 和 Alt)处于按下的状态。
public static Keys ModifierKeys { get; }
Keys枚举值非常丰富,请看MSDNh、Opacity 获取或设置窗体的不透明度级别。
i、Locked属性在设计Form1中窗体的属性中,但MSDN没有对它做解释,只是在派生类中有该属性。应该怎么用?
j、Enabled 获取或设置一个值,该值指示控件是否可以对用户交互作出响应。
方法
a、对ShowDialog()进行了初探,如A.ShowDialog(B) 方法是 将窗体A窗体显示为具有指定所有者(所有者是窗体B)的模式对话框,即B拥有A,A是B的对话框。A和B都是 不同窗体类的对象的引用符。
如果直接用ShowDialog(),没有参数,则将窗体显示为模式对话框,并将当前活动窗口设置为它的所有者。
可以使用此方法在应用程序中显示模式对话框。调用此方法时,直到关闭对话框后,才执行此方法后面的代码。可以将 DialogResult 枚举值之一分配给对话框,方法是将该值分配给窗体上 Button 的 DialogResult 属性或通过使用代码设置窗体的 DialogResult 属性。此方法随后返回该值。可以使用此返回值确定如何处理对话框中发生的操作。例如,如果关闭了对话框,并通过此方法返回了 DialogResult.Cancel 值,则可防止执行在调用 ShowDialog 之后的代码。
当窗体显示为模式对话框时,单击“关闭”按钮(窗体右上角带 X 的按钮)会隐藏窗体并将 DialogResult 属性设置为 DialogResult.Cancel。与无模式窗体不同,当用户单击对话框的关闭窗体按钮或设置 DialogResult 属性的值时,.NET Framework 不调用 Close 方法。窗体转而可以隐藏并可重新显示,而不用创建该对话框的新实例。因为未关闭显示为对话框的窗体,所以在您的应用程序不再需要该窗体时,必须调用该窗体的 Dispose 方法。
public void ShowMyDialogBox() { Form2 testDialog = new Form2(); Form1 form1 = new Form1(); // Show testDialog as a modal dialog and determine if DialogResult = OK. DialogResult dia=testDialog.ShowDialog(form1); form1.usernameTextBox.Text = dia.ToString(); form1.ShowDialog(); testDialog.Show();//关掉的窗体又出来了! //if (testDialog.ShowDialog() == DialogResult.OK) //{ // // Read the contents of testDialog's TextBox. // this.usernameTextBox.Text =testDialog.textBox1.Text; //} //else //{ // this.usernameTextBox.Text = "Cancelled"; //} //testDialog.Dispose();//要想真正关掉模式窗体,必须用Dispose() }
具体试验程序在虚拟机中的“D:\DOT NET\叩响C#之门\第13章 Windows窗体编程\HelloWorld\HelloWorld”项目中。
b、Hide()方法 对用户隐藏控件。MSDN上的这段代码不光说米高Hide()的用法,也引入一个新知识(ModifierKeys ),下面的代码示例在单击某按钮的同时按 Ctrl 键时隐藏该按钮。
private void button2_Click(object sender, System.EventArgs e) { /* If the CTRL key is pressed when the * control is clicked, hide the control. */ if (Control.ModifierKeys == Keys.Control) { ((Control)sender).Hide(); } }
c、Close()方法 关闭窗体。某些时候效果上类似于Hide()方法,主要是在关闭模式窗体时。
窗体关闭后,关闭在该对象内创建的所有资源并且释放该窗体。通过处理 Closing 事件,并设置作为参数传递给事件处理程序的 CancelEventArgs 的 Cancel 属性,可以防止在运行时关闭窗体。如果要关闭的窗体是应用程序的启动窗体,则该应用程序结束。
Close 时不释放窗体的一种情况是,窗体属于多文档界面 (MDI) 应用程序的一部分且是不可见的(在关闭模式窗体时,应该也是如此)。在这种情况下,您需要手动调用 Dispose,将窗体的所有控件都标记为进行垃圾回收。
在显示为无模式窗口的 Form 上调用 Close 方法时,不能调用 Show 方法使窗体可见,因为窗体的资源已被释放。若要隐藏窗体然后又使其可见,请使用 Control.Hide 方法。
d、Show()方法,普通的Show()方法 向用户显示控件。 当Show()内有了参数 窗体实例时,则向用户显示具有指定所有者的窗体,如窗体A.Show(窗体B),表示将A显示出来,同时指定B为A的所有者。这一用法很有趣。
二、最简单的一些控件
1、Button
Button 构造函数
public Button ()
首先,创建按钮button1相当于在窗体类中声明了一个Button类的对象button1,即生成了一个引用符,这个引用符变量是Form1类的私有成员变量。
private System.Windows.Forms.Button button1;
第二步,Form1.Designer.cs中的InitializeComponent()中需增加如下代码:
this.button1 = new System.Windows.Forms.Button();//初始化实例,并把对象的地址赋给引用符button1
this.Controls.Add(this.button1);//在对Form1窗体的初始化程序中加入该句,将button1添
引入Controls属性的解释:
System.Windows.Forms.Control类 定义控件的基类,控件是带有可视化表示形式的组件。
Control.Controls属性是个集合类的属性 获取包含在控件内的控件的集合。
public ControlCollection Controls { get; }
属性值是一个 Control.ControlCollection,它表示控件内包含的控件的集合。
这里涉及到集合类的知识,可以先学后章节,再回来分析
Control 可以充当控件集合的父级。例如,将多个控件添加到 Form 时,每一个控件都是分配给该窗体的 Controls 属性的 Control.ControlCollection 的成员,Controls 属性派生于 Control 类。
可以使用 Control.ControlCollection 类中的可用方法,在分配给 Controls 属性的 Control.ControlCollection 中操作控件。
将多个控件添加到父控件时,建议在初始化要添加的控件之前调用 SuspendLayout 方法。将控件添加到父控件之后,调用 ResumeLayout 方法。这样就可以提高带有许多控件的应用程序的性能。
上面Add()方法前的Controls属性是继承自Control类来的,Controls属性是个集合类ControlCollection,包含在控件内的控件的集合,因此通过Add()来添加集合类内的元素,这些元素又都是Control类的对象。
常见属性
AutoSizeMode、AutoSize、BackColor、Enabled、Visible用法与窗体一致
常见方法
Hide()、Show()方法与窗体一致
常用事件
Click 在单击控件时发生。
2、标签(Label)主要用属性Text
3、超链接标签(LinkLabel)
常用属性: LinkColor 超链接访问前的颜色,默认为蓝色;ActiveLinkColor 超链接单击时的颜色;VisitedLinkColor超链接访问后的颜色
LinkVisited 记录超链接是否被访问过,ture表示已访问;LinkBehavior 设置超链接中下画线的样式,共有四种
LinkArea 指定那部分文本为超链接
常用事件: LinkClicked单击超链接时产生事件
在使用时会用到System.Diagnostics命名空间的Process类,该类的Start()方法用于打开文件夹或应用程序。该方法有一个或两个参数,在本随笔的上文已有阐述。
4、文本框(TextBox)
常见属性:
Text 文本框内显示的文本(即默认文本)
SelectedText 文本框内被选中的文本
SelectedTextStart 被选中的第一个字符
Multiline 为true时文本可多行,为false时只能单行,默认为false
AcceptsRetrun 为true时Enter建的功能为换行,否则Enter建的功能是确定
ReadOnly 为true时文本不能修改,文本框显示灰色背景
ScrollBars滚动条样式
TabIndex用Tab键激活控件的顺序 怎么用?
PasswordChar使文本框变为密码框,用指定字符屏蔽用户输入的密码
事件
TextChanged文本改变时产生
方法
Clear()清除文本框中的所有文本
Copy()将选定的文本复制到“剪贴板”中
Cut()将选定的文本剪切到“剪贴板”中
Paste()用剪切板中的文本替换选定的文本
ResetText()将Text属性重置为默认值
Redo()重复上一个编辑操作
Undo()撤销上一个编辑操作
5、单选按钮
属性:Checked单选按钮是否被选中
事件:CheckedChanged当单选按钮被选中时发生
6、复选框
属性:Text复选框旁显示的文本
Checked 复选框是否被勾选
CheckState复选框的状态,分Checked、Unchecked和Indeteminate(不确定)
事件:
CheckedChanged当复选框被勾选时发生(取消勾选时也发生)
CheckStateChanged当CheckState属性改变时发生
7、数字输入框(NumericUpDown)
数字输入框是用来帮助我们输入特定范围内的数字。既可以在文本框内直接输入数字,也可以用上下按钮进行微调。
属性:
Value 数字输入框内显示的数值(类型为decimal)
Increment (名词:增加,增量)用上下按钮进行微调时的变化量
Maximum 可输入的最大值
Minimum 可输入的最小值
UpDownAlign 设定上下按钮的位置,有左右两种选项
事件:
ValueChanged数值改变时发生
涉及其他类的知识:
Font类 属于System.Drawing空间
Font (String, Single) 是其中的一个构造函数,参数为字体和字大小
public Font (
string familyName,
float emSize
)
private void numericUpDown1_ValueChanged(object sender, EventArgs e) { int fontSize = Convert.ToInt32(numericUpDown1.Value); Font myFont = new Font("黑体",fontSize); label1.Font = myFont; }
8、群组框(GroupBox)
如果窗体上有很多空间,可以用群组框进行分组,功能相关的控件分为一组可以使界面更加清晰。
属性:Controls 由群组框包含的所有控件组成的集合
在Form1.Designer.cs的代码中对 GroupBox属性的初始化的代码中会有类似下述的语句,将包含在GroupBox内的控件添加到群组框内的控件集合中。
this.groupBox1.Controls.Add(this.MaleRadio); this.groupBox1.Controls.Add(this.FemaleRadio);
9、面板(Panel)
面板(Panel)和群组框一样,也用来把控件进行分组,它和群组框的区别是群组框可以有标题,而面板可以有滚动条。
属性:
AutoScroll 是否显示滚动条
Controls 有群组框中包含的控件组成的集合
BackgroundImage 设置背景图片
涉及其他类的知识:
String 类中 Substring()方法 从字符串中截取子串。