Form窗口

Winform代码中,窗体(窗口)对应Form对象。窗体包含了许多基础功能,包括隐藏和显示、生命周期、大小和位置、菜单等。创建一个Winform工程后,Visual Studio已经帮我们创建了一个默认的Form1.cs,我们也可以继续创建新的窗口,以模态或非模态形式实例化并显示。

这篇笔记我们对Winform中的窗体进行介绍。

窗体设计器和逻辑代码的关系

我们在Visual Studio设计器中拖拽控件,这些布局内容放置在XXForm.Designer.cs中,而我们针对按钮点击等处理逻辑则放在XXForm.cs中,虽然分写在两个文件中,但实际上它们是一个类,两者互为partial class,分开写是为了不让设计器自动生成的代码和我们手写的代码混杂在一起而互相干扰。

显示和关闭窗体

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

Winform中,显示窗体分为两种方式,模态非模态,这可以调用ShowDialog()Show()方法实现。下面例子中,我们点击按钮时,分别以模态和非模态方式展示窗体。

private void showNewFormButton_Click(object sender, EventArgs e)
{
    NewForm newForm = new NewForm();
    newForm.ShowDialog(this);
}
private void showNewFormButton_Click(object sender, EventArgs e)
{
    NewForm newForm = new NewForm();
    newForm.Show(this);
}

ShowDialog()Show()方法都可以传入当前窗体作为参数,当前窗体被称为Owner(拥有者窗体)。模态窗体会禁用当前应用程序中的所有其它窗口,用户必须先关闭模态窗体才能操作其它窗口,同时调用ShowDialog()处的代码会被阻塞;而非模态窗体则没有这种行为,也不会阻塞代码流程的执行。

模态窗体的表现其实更像是对话框。实际上,一些常用的模态窗体如文件选择框、颜色选择框等Winform都已经内置了,关于内置对话框我们将在后续章节介绍。

关闭窗体最简单的方式是用户直接点击关闭按钮,不过如果窗体需要被其他方式关闭,我们可以手动调用Close()方法。

private void closeButton_Click(object sender, EventArgs e)
{
    this.Close();
}

注意Close()Hide()的区别:Close()会真正关闭并释放窗体资源(对Show()打开的窗体会触发Dispose),之后不能再对其调用Show();而Hide()只是将窗体隐藏(等价于Visible = false),窗体对象仍然存活,可以随时再次调用Show()显示。

窗体生命周期

窗体有几个比较重要的生命周期需要我们了解。

事件 说明
Load 窗体首次加载(即将显示)时触发,可用于加载初始化数据
Shown 窗体首次显示完成后触发,只在窗体第一次显示时触发一次
Activated 窗体获取焦点成为活动窗体时触发
Deactivate 窗体失去焦点时触发
FormClosing 窗体即将关闭时触发,可在此处打断关闭操作
FormClosed 窗体完成关闭后触发

窗体打开阶段:执行窗体构造函数时,只会执行InitializeComponent()方法初始化所有子控件,此时窗体还没有显示,也不会触发任何激活相关事件。这个方法通常是由Visual Studio设计器生成并维护的,我们不应修改这个方法的任何行为。当窗体第一次被Show()ShowDialog()显示时,首先触发Load事件,窗体包含的初始化逻辑(如加载数据)应该写在Load事件的处理函数中,因为此时窗体的句柄已经创建、子控件已就绪;随后窗体被激活,Activated事件触发;最后当窗体显示完毕,Shown事件触发。综上所述,首次显示的完整顺序为:构造函数 -> Load -> Activated -> Shown

窗体激活和非激活:如果用户使用Alt+Tab等操作切离当前窗体,窗体将变为非活动窗体,Deactivate事件将被触发,假如我们在开发一个游戏程序,那么此时就是自动暂停游戏的时候;当用户再次切回窗体时,Activated将被再次触发。ActivatedDeactivate在整个生命周期中可能反复触发多次。

窗体关闭阶段:当用户试图关闭窗体时,FormClosing将被先触发,注意此时窗体还没真的被关闭,此时我们可以通过代码弹出确认对话框,设置FormClosingEventArgs.Cancel属性为true即可取消关闭操作,给用户机会改变主意。如果窗体被确认关闭,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菜单栏控件,菜单栏可以包含多个ToolStripMenuItem菜单项,每个菜单项又可以包含多个子菜单项,子菜单项也可以嵌套更多的子菜单,形成菜单树形结构。

如果想为菜单项绑定事件,和之前类似,在Visual Studio设计器中双击即可,这里就不多介绍了。

此外,我们还可以给菜单项绑定访问键,下面例子中我们给文件菜单项绑定Alt + F访问键,这需要将文件菜单项名字改为文件(&F),名字中的(&F)会被Winform框架自动识别为访问键。

什么是访问键:其实大家都见过访问键,但极少使用,访问键到底是怎么来的?在鼠标普及之前,计算机操作完全依赖键盘(如DOS命令行、WordStar等字符界面系统),随着Xerox PARC(施乐帕洛阿尔托研究中心)开创图形界面、苹果Macintosh和微软Windows的发布,计算机进入GUI时代,如何让习惯了全键盘高效操作的用户在没有鼠标的情况下快速导航,成了当时界面设计的核心课题之一,IBM在1987年提出了CUA(Common User Access,通用用户访问)规范,其中明确规定了使用Alt键激活菜单栏、菜单文本中使用带下划线的字母作为导航锚点等交互模式,微软在Windows 1.0中全面采纳并推广了这一逻辑,这就是我们这里说的访问键。在今天看来“没有鼠标的计算机”虽然听起来有些无厘头,但它的出现确实有其历史背景。

除了ToolStripMenuItem菜单项,MenuStrip上还可以添加ToolStripComboBox下拉组合框、ToolStripTextBox文本输入框,子菜单中还可以添加ToolStripSeparator分隔线。

ToolStrip 工具条

ToolStrip工具条通常是位于菜单下方的一组图标按钮,我们可以在其中添加ToolStripButton工具按钮,这些工具按钮也可以绑定点击事件。

除了ToolStripButton工具按钮,ToolStrip工具条中还可以添加ToolStripLabel工具栏标签、ToolStripSplitButton拆分按钮、ToolStripDropDownButton下拉按钮、ToolStripSeparator分隔线、ToolStripComboBox下拉组合框、ToolStripTextBox文本输入框、ToolStripProgressBar进度条控件。

StatusStrip 状态条

StatusStrip通常位于窗体底部,用于展示应用程序当前的状态。StatusStrip中可以添加多种子项,例如StatusLabel常用于作为窗体底部的状态说明文字信息。

除了StatusLabel状态标签,StatusStrip状态条中还可以添加ToolStripProgressBar进度条控件、ToolStripDropDownButton下拉按钮、ToolStripSplitButton拆分按钮。

ContextMenuStrip 右键上下文菜单

ContextMenuStrip是右键菜单,它的菜单项和顶部菜单一样,都是ToolStripMenuItem,我们可以在设计器中向窗体内拖入一个ContextMenuStrip添加一组右键上下文菜单。

除了ToolStripMenuItem菜单项,ContextMenuStrip上还可以添加ToolStripComboBox下拉组合框、ToolStripTextBox文本输入框,以及ToolStripSeparator分隔线。

不过ContextMenuStrip本身不会自动显示,它需要绑定到一个控件上才能触发。例如在窗体的属性中,我们可以编辑行为 -> ContextMenuStrip,将其设置为我们创建的右键上下文菜单,此时在窗体上点击右键,我们就会看到右键上下文菜单出现。

此外,我们也可以通过代码触发ContextMenuStrip,下面例子中我们使用按钮来触发,展示ContextMenuStrip需要调用它的Show方法,传入的第1个参数是按钮对象,它用于定位ContextMenuStrip的显示位置,后面两个参数是相对于按钮对象的偏移位置,我们将其x偏移设置为0,y偏移设置为按钮的高度,这样做是为了避免菜单挡住按钮。

private void showContextMenuButton_Click(object sender, EventArgs e)
{
    contextMenuStrip.Show(showContextMenuButton, 0, showContextMenuButton.Height);
}

MDI虚拟窗体

MDI(Multiple Document Interface,多文档界面)是一种稍微复杂的界面形式,其中一个主窗体作为容器,内部可以容纳多个子窗体,子窗体被限制在主窗体的客户区内显示,子窗体具有独立的标题栏、关闭、最小化、最大化等功能。早期的Office、Photoshop等软件都采用了这种形式。

对于主窗体,我们需要在设计器中设置IsMdiContainer属性为True,这样它才是一个MDI主窗体。

我们可以在主窗体上添加一个菜单按钮,其处理函数如下,代码中我们实例化了子窗体ChildForm并设置其MdiParent属性为this,即主窗体对象,然后调用Show()方法显示子窗体。

private void openChildFormToolStripMenuItem_Click(object sender, EventArgs e)
{
    ChildForm childForm = new ChildForm
    {
        MdiParent = this
    };
    childForm.Show();
}

MDI窗体效果如下图所示。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。