Winform窗体应用程序入门

Windows Forms(后续简称Winform)是Microsoft基于.NET框架推出的经典桌面图形用户界面(GUI)开发技术,它能通过直观的可视化拖拽控件与灵活的事件驱动机制,大幅降低Windows应用程序的开发门槛。凭借极高的开发效率、丰富的三方生态组件、极低的内存开销以及对Windows原生API的无缝集成,WinForm至今仍是快速构建企业内部管理系统、工业控制软件及桌面工具软件的行业首选方案之一。

开始开发Winform工程

打开Visual Studio,创建一个「Windows 窗体应用」。

创建工程时,我们可以根据我们自己的需要选择使用经典的.NET Framework还是新版的.NET(我们这里使用的是.NET)。不过Winform是个相对较老的框架,两种运行时都能开发Winform程序,基本套路大致相同,但也存在一些实际差异需要注意:

项目文件csproj格式:.NET Framework使用旧版.csproj格式,默认包含AssemblyInfo.csApp.configSettings.settings等文件;新版.NET使用SDK风格的精简.csproj,程序集信息可直接写在项目文件中,配置文件也改用了新的机制。

API差异:新版.NET移除了一些非常过时的API,如果参考的是比较古老的资料,可能会遇到编译错误。

设计器支持:新版.NET的Winform设计器曾长期不完善,不过从.NET 6版本起已基本可用,但极个别第三方老控件可能仍只支持.NET Framework。

发布区别:迄今为止,Windows10/11仍自带.NET Framework 4.8版本,这意味着我们面向.NET Framework 4.8开发的Winform程序发布时不需要自带运行时依赖,发布包可以做到极小(几百KB),而.NET运行时系统是不自带的,带运行时发布包体积很大(一百至二百MB),且Winform项目不支持对.NET运行时进行裁剪或AOT发布模式。

对于入门学习而言两种运行时都可以,后续示例代码我们将尽量保证在两个环境中均可以运行。

项目创建完成后,如下图所示,这是一个窗口(即默认创建的Form1)的界面设计器。我们可以把「工具箱」固定到IDE面板中,方便把控件往窗口上拖拽。其它面板包括工程视图和所选控件的属性值,我们可以对其进行编辑。

这里我们先不着急往下做,先看一眼Visual Studio为我们生成的工程模板,它大致会是如下目录结构。

Gacfox.Demo
 │_ Form1.cs                    # 窗体业务逻辑代码
 │_ Form1.Designer.cs           # 窗体UI设计器自动生成的代码
 │_ Form1.resx                  # 窗体资源文件
 │_ Gacfox.Demo.csproj          # 项目文件
 │_ Program.cs                  # 程序入口文件
 |_ bin                         # 编译输出目录
 |_ obj                         # 中间文件目录
 |_ Properties                  # 包含与应用程序的元数据和资源相关的文件
     |_ Resources.resx          # 全局应用程序资源文件
     |_ Resources.Designer.cs   # 全局应用程序资源文件(代码部分)
  • Form1.cs:Form1是窗体对应的类,我们编写的控件事件处理函数就会写在这里。
  • Form1.Designer.cs:该类与Form1互为partial class,其内容是Visual Studio设计器为我们自动生成的UI代码,我们在设计器中对界面的编辑操作都会反映到这个文件中,该代码文件是由Visual Studio设计器维护的,除非我们的一些改动造成设计器报错,不然我们应尽量避免手动编辑.Designer.cs文件
  • Resources.resx:它相当于一个资源文件的索引,我们引用的图片等资源就是通过这个配置文件加载的,其内容为XML格式,不过它的具体内容是由Visual Studio维护的,我们不需要关心也不用手动修改。
  • Program.cs:程序的入口文件,其中包含Main()函数。

入口类Program很简单,它初始化了一些参数并实例化Form1窗体类。

namespace Gacfox.Demo;

internal static class Program
{
    /// <summary>
    ///  The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        ApplicationConfiguration.Initialize();
        Application.Run(new Form1());
    }
}

注意对于Winform程序,主线程必须标注[STAThread],这个特性(Attribute)使得主线程使用单线程单元 (Single-Threaded Apartment, STA) 模式,Winform中主线程必须是STA线程。Application.Run(new Form1())启动了应用程序的消息循环,它会不断从系统消息队列中取出消息(鼠标点击、键盘输入、重绘请求等)并分发给对应的窗体处理,消息循环是Winform程序运转的核心,它将持续运行直到主窗体被关闭。

接下来我们开始窗体设计,我们在设计器中大致拖几个控件进去,组成一个「登录框」。

在属性面板中我们可以修改控件的各种属性,其中要注意的是我们一定要把Design -> (Name)起一个合适的名字且这个名字在当前窗体类中不能重复,它是控件对应的成员变量名。修改属性并没有什么难度,Visual Studio中甚至都有属性的中文说明,这里就不展开介绍了。Winform中的属性很多,不认识的可以参考文档。

我们把窗体大致修改成这个样子。

现在我们为按钮绑定事件处理函数。我们双击设计器拖进去的按钮控件,这会跳转到按钮的默认事件处理函数,或者选中按钮控件后点击属性面板中右下角的闪电图标,双击Click事件,效果也是一样的。Visual Studio为我们生成了Click事件对应的回调函数,其命名规则为控件变量名_事件名(这不是通常的帕斯卡命名法),这个有些奇怪的命名风格是历史原因造成的,我们遵循即可。

private void loginButton_Click(object sender, EventArgs e) {}

下面我们假装写一个登录的逻辑。

private void loginButton_Click(object sender, EventArgs e)
{
    string username = usernameTextBox.Text;
    string password = passwordTextBox.Text;
    if (username == "root" && password == "123456")
    {
        MessageBox.Show("登录成功");
    }
    else
    {
        MessageBox.Show("登录失败");
    }
}

点击「运行」按钮,结果如下。

查看窗体控件大纲

如果我们的界面比较复杂,纯靠设计器可能比较难以理清控件的层次结构。Visual Studio中点击菜单栏视图->其他窗口->文档大纲,我们可以打开控件大纲窗口,方便我们查看当前窗体中的控件层次结构。

我们可以在这个大纲列表中进行点选、删除控件等操作,操作也会反应到设计器主界面中。

使用设计器 vs 纯代码

很多人说学习Winform、Qt等GUI框架时应该避免使用设计器,这是完全错误的观点。设计Winform界面需要调用大量和控件紧密相关的属性和API,这些API我们根本没必要刻意记住,用得少的靠IDE辅助即可,用得多的自然会留下印象,使用设计器能省略这些繁琐的步骤提高开发效率,不需要“没苦硬吃”。Visual Studio设计器能够帮我们生成界面代码,使用非常方便,它能覆盖绝大部分的需求。

然而,我们并不能说设计器适用于所有场景,当我们需要非常灵活的动态控制控件和事件绑定时,使用设计器就非常不灵活了。此外,对于功能极其复杂的软件,使用纯代码来开发也比设计器更靠谱。

不过在学习阶段,我们还是应该善于使用设计器并分析那些自动生成的样板代码,只要掌握了Winform开发的基本套路,无论使用设计器还是纯代码都不会有太大困难。

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