Form窗口
Winform代码中,窗体(窗口)对应Form对象。窗体包含了许多基础功能,包括隐藏和显示、生命周期、大小和位置、菜单等。创建一个Winform工程后,Visual Studio已经帮我们创建了一个默认的Form1.cs,我们也可以继续创建新的窗口,以模态或非模态形式实例化并显示。
这篇笔记我们对Winform中的窗体进行介绍。
窗体设计器和逻辑代码的关系
我们在Visual Studio中拖控件,这些布局内容放置在XXForm.Designer.cs中,而我们针对按钮点击等处理逻辑则放在XXForm.cs中,虽然分写在两个文件中,但实际上它们是一个类,后者是前者的partial class,分开写是为了不让设计器自动生成的代码和我们手写的代码混杂在一起而互相干扰。

显示和关闭窗体
实际开发中,我们的程序很有可能拥有不止一个窗口,比如一个业务系统,可能会以弹出窗口的形式让用户填写一个表单,这在传统软件中很常见。如果想要添加窗体,在工程上点击右键,选择添加->Windows窗体即可。

Winform中,显示窗体分为两种方式,模态和非模态,这可以调用ShowDialog()和Show()方法实现。下面例子中,我们点击按钮时,分别以模态和非模态方式展示显示窗体。
private void btnShowNewForm_Click(object sender, EventArgs e)
{
NewForm newForm = new NewForm();
newForm.ShowDialog(this);
}
private void btnShowNewForm_Click(object sender, EventArgs e)
{
NewForm newForm = new NewForm();
newForm.Show(this);
}
ShowDialog()和Show()方法都可以传入当前窗体作为参数,当前窗体被称为Owner(拥有者窗体),模态窗体会禁止拥有者窗体的操作直到模态窗体关闭,代码也会被阻塞,而非模态窗体则没有这种行为,也不会阻塞代码流程的执行。
模态窗体的表现其实更像是对话框。实际上,一些常用的模态窗体如文件选择框、颜色选择框等Winform都已经内置了,关于内置对话框将在后续章节介绍。
关闭窗体最简单的方式是用户直接点击关闭按钮,不过如果窗体需要被其他方式关闭时,我们可以手动调用Close()方法。
private void btnClose_Click(object sender, EventArgs e)
{
this.Close();
}
窗体生命周期
窗体有几个比较重要的生命周期需要我们了解。
| 事件 | 说明 |
|---|---|
| Load | 窗体加载时触发,可以用于加载初始化数据 |
| Shown | 窗体显示时触发 |
| Activated | 窗体获取焦点时触发 |
| Deactivate | 窗体失去焦点时触发 |
| FormClosing | 窗体关闭中触发 |
| FormClosed | 窗体完成关闭触发 |
窗体打开阶段:执行窗体构造函数时,新的窗体对象就会被激活,窗体初始化过程中首先会调用设计器自动生成代码中的InitializeComponent方法来初始化所有子控件,这个方法是由Visual Studio维护的,我们不应修改这个方法的任何行为。如果窗体包含一些其它初始化逻辑,我们应该在Load()生命周期函数中编写。随后窗体将被激活,此时Activated()会被回调。窗体激活后,Shown()会被触发。
窗体激活和非激活:如果用户使用Alt+Tab等操作切离当前窗体,窗体将变为非活动窗体,Deactivate()方法将被触发,假如我们在开发一个游戏程序,那么此时就是自动暂停游戏的时候。当用户再次切回窗体时,Activated()将被再次触发。
窗体关闭阶段:当用户试图关闭窗体时,FormClosing()将被先触发,注意此时窗体还没真的被关闭,我们可以通过代码弹出确认对话框,设置FormClosingEventArgs.Cancel属性给用户机会改变这一操作。如果窗体被确认关闭,FormClosed()将被触发,我们可以在此做一些收尾工作。
下图展示了这些时间的发生顺序,从窗体构造开始到窗体关闭结束。

代码中,我们可以编写对应事件的委托函数,在对应生命周期执行特定逻辑。通常来说,我们可以直接在设计器中创建窗体Load对应的委托函数。
private void Form1_Load(object sender, EventArgs e)
{
MessageBox.Show("窗体加载时触发,可以用于加载初始化数据");
}
实际上,查看Form1.Designer.cs文件,我们可以发现Form1_Load()函数会以委托形式绑定到Load事件上,它定义在窗体的基类Form内。
this.Load += new System.EventHandler(this.Form1_Load);
窗体控件
我们经常使用Windows操作系统都知道,“标准”窗体上总是有菜单、工具条、状态条等控件,Winform中这些控件都可以添加到Form上。
MenuStrip 菜单
Winform窗体中,我们可以为窗体添加MenuStrip菜单条控件,主菜单条可以包含多个菜单项,每个菜单项又可以包含多个子菜单项,子菜单项也可以嵌套更多的子菜单,形成菜单树形结构。

MenuStrip菜单项可以设置图片、快捷键等,此外菜单项绑定点击事件的方法和按钮相同,这里就不展开介绍了。
ToolStrip 工具条
ToolStrip工具条通常是位于菜单下方的一组图标按钮,这些图标按钮也可以绑定点击事件。

StatusStrip 状态条
StatusStrip通常位于窗体底部,用于展示应用程序当前的状态。StatusStrip有多种形式,常用的包括:StatusLabel文本,ProgressBar进度条和DropDownButton可展开的按钮。

下面例子代码中,我们编写了一个按钮点击的回调函数,它设置了文本状态为Success,进度条状态为100%。
private void btnUpdateStatus_Click(object sender, EventArgs e)
{
slStatus.Text = "Success";
spProgress.Value = 100;
}
MDI虚拟窗体
MDI虚拟窗体是一种稍微复杂的控件,它的子窗体不是真正的窗体,而是绘制到主窗体中,不过子窗体仍具有关闭、最小化、放大等功能。这里我们看一个例子,代码中,MainForm是MDI主窗体,它上面有一个菜单按钮,点击后即可打开虚拟子窗体ChildForm。
对于主窗体,我们需要在设计器中设置IsMdiContainer属性为True,这样它才是一个MDI主窗体。

我们可以在主窗体上添加一个菜单按钮,其处理函数如下,代码中我们实例化了子窗体ChildForm并设置其MdiParent属性为this,即主窗体对象,然后调用Show()方法显示子窗体。
private void ToolStripMenuItem_Click(object sender, EventArgs e)
{
ChildForm childForm = new ChildForm
{
MdiParent = this
};
childForm.Show();
}
MDI窗体效果如下图所示。
