参考文献
[1] Microsoft. 适用于 Windows 窗体的 .NET 桌面指南[EB/OL]. [2022-9-17]. https://docs.microsoft.com/zh-cn/dotnet/desktop/winforms/getting-started-with-windows-forms?view=netframeworkdesktop-4.8.
[2] 明日科技. C#从入门到精通[M]. 第5版. 北京:清华大学出版社, 2019.
0 WinForms简述
Windows 窗体(WinForms)应用程序是基于 .NET Framework 的智能客户端组件,可通过 Visual Studio 创建。windows窗体应用程序可显示信息、请求用户输入以及通过网络与远程计算机通信,构建丰富的交互式用户界面。
1窗体
窗体(Form)是一种可视图面,可在其上对用户显示信息。通常通过将控件放置在窗体上并开发对用户操作(如鼠标单击或按键)的响应(即事件)来生成 Windows 窗体应用程序。窗体是对象,基于 System.Windows.Forms 命名空间中的类生成,窗体类定义了生成窗体的模板,每实例化一个窗体类,就产生一个窗体。
MDI窗体:MDI(Multiple Document Interface,多文档窗体)和单文档SDI相对,二者区别在于MDI通常是一个带有菜单的主界面,允许用户同时打开多个窗体;而SDI只能打开一个窗体。MDI窗体示例见下图。
MDIparent:父窗体 MDIchild:子窗体
多窗体项目必须要设置先运行的窗体,即启动窗体。项目的启动窗体是在Program.cs文件的Run()方法中设置的。Run()方法用于在当前线程上开始运行标准应用程序,并使指定窗体可见。
public static void Run(Form mainForm) //mainForm为要设为启动窗体的窗体。
窗体的组成要素可通过窗体的“属性”面板(或代码)进行设置。常见属性设置见下表。
属性 | 功能 |
---|---|
Icon | 窗体图标更换 |
FormBorderStyle | 标题栏隐藏 |
StartPositon | 窗体显示位置 |
Size | 窗体大小 |
BackgroundImage | 设置图像背景 |
在【属性】中找到这几个值比较麻烦,因为属性栏并没有提供搜索选项。不过可以将属性【按分类顺序】改为【按字母顺序】,然后按照首字母查找。
通过new创建了一个新窗体后,该窗体的Visible属性默认是false,顾不进行显示。有多种方法可以实现子窗体的打开与隐藏。
Form form = new Form();
form.Show(); //显示窗体,这种方法显示的子窗体与主窗体互不影响
form.Showdialog(); //显示窗体,只能在关闭子窗体后才能操作主窗体
form.Activate(); //在最前面显示窗体
form.Hide(); //隐藏窗体
Windows是事件驱动的操作系统,对Form类的任何交互都是基于事件来实现的。Form类提供了大量的事件用于响应对窗体执行的各种操作。几种窗体常用事件。
事件 | 触发方式 |
---|---|
click(单击) | 单击窗口时触发 |
load(加载) | 窗体加载时触发 |
FormClosing(关闭) | 窗体关闭时触发 |
2组件
组件(component)在某些方面与控件相似, 与控件不同的地方在于,组件并非用户可在窗体中查看的项目,相反,这将提供可使用代码触发的某些行为。控件是带有可视化表示形式的组件。
3控件
控件(Control)是离散的用户界面元素,用于显示数据或接受数据输入。控件是带有可视化表示形式的组件。
Windows应用程序控件的基类是位于System.Windows.Forms命名空间的Control类。Control类定义了控件类的共同属性、方法和事件,其他的控件类都直接或间接地派生自这个基类。
常用控件可以分为文本类控件、选择类控件、分组控件、菜单控件、工具栏控件以及状态栏控件。
自定义控件名称需要遵循控件的命名规范。
方法在引用时需在【事件】中进行配置(在控件中添加事件):
此时会在Form.cs中被引用的方法左上角会自动显示:
4事件
事件(Event)是可以通过代码响应或“处理”的操作。 事件可由用户操作(例如单击鼠标或按某个键)、程序代码或系统生成。
事件驱动的应用程序执行代码以响应事件。 每个窗体和控件都公开一组预定义事件,你可根据这些事件进行编程。 如果发生其中一个事件并且在相关联的事件处理程序中有代码,则调用该代码。
对象引发的事件类型会发生变化,但对于大多数控件,很多类型都是通用的。 例如,大多数对象都会处理 Click 事件。 此外,许多事件会与其他事件同时发生。
事件处理程序是绑定到事件的方法, 引发事件时,事件处理程序内的代码将会执行。
private void inputTextBox1_KeyPress(object sender, EventArgs e)
{
//主体
}
事件处理程序提供下述两类参数。
- object sender:sender提供对引发事件的对象的引用,上例中控件对象为inputTextBox1;
- EventArgs e:e传递特定于要处理的事件的对象,上例中为获取当前按下来的键值。
当第二个参数具有相同的对象类型时,可使用同一事件处理程序来处理这两个事件。
5线程
5.1概述
默认情况下,系统为应用程序分配一个线程(thread),即主线程。主线程就是Main()方法所在的线程,主线程即消息循环。构造函数中的Run()方法的功能为在当前线程上启动应用程序消息循环。
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
一般情况下,所谓UI线程就是主线程(除非用户进行了修改),即使启动多个窗口也是都在主线程上,并非一个窗口对应一个线程。
单线程环境下,如果做复杂处理,则会出现假死状态(即堵塞),因为一条线程同时只能做一件事。可以通过设计多线程来处理耗时的任务,这样就不会使主界面出现无响应的情况,新创建的线程被称为子线程或工作线程。
5.2跨线程访问
控件由主线程创建,由于程序默认是不允许跨线程访问的,新线程想要访问主线程资源,最简单的方式是在要访问的窗口的构造函数中关闭跨线程访问检查,见下。
public Form1()
{
InitializeComponent();
CheckForIllegalCrossThreadCalls = false; //关闭跨线程访问检查。
}
6计时器
计时器(Timer)按用户定义的时间间隔引发事件。.NET支持多个计时器类。
-
System.Windows.Forms.Timer
简述:该计时器是一个Windows 窗体组件,被设计为单线程环境,基于UI线程运行,在Tick事件中执行相关操作。若Tick事件中执行任务量过多会导致堵塞,现象为UI无响应。如果单次执行时间超过设置的间隔时间,则下次执行会延后直至本次执行完成,因而精度较差。
精度:55ms -
System.Threading.Timer
简述:该计时器提供以指定的时间间隔对线程池线程执行方法的机制。 -
System.Timers.Timer
简述:该计时器基于服务器,用于多线程环境中的工作线程,可以在线程之间移动以处理引发的Elapsed事件。
精度:1ms
7基础操作
7.1新建项目
有C#和VB(.NET Framework),注意区别。
新建项目并添加窗体后,在解决方案资源管理器中生成了Form.Designer.cs、 Form.cs 、Form1.resx和Program.cs四个文件。
- Form.cs中存放用户编写的窗体操作代码;
- Form.Designer.cs 是对窗体画面布局的设计代码,由Visual Studio自动生成;
- Form1.resx包含表单所保存的图像等资源;
- Program.cs中存放main方法,是程序的入口点;
要注意的是在解决方案资源管理器中双击Form.cs只会弹出设计器而非代码页面,需右击Form.cs,在弹出的菜单中点击【查看代码】:
新建一个winform应用程序,默认会带入以下空间的引用:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
将自动生成**分布类(partial class)**及其构造函数:
namespace test
{
public partial class Form1 : Form //分布类
{
public Form1() //构造函数
{
InitializeComponent();
}
}
}
可以拆分一个类,使其分布于多个独立文件中,该类即称作分布类,使用 partial 关键字修饰符。VS在创建windows窗体时会拆分类定义,这样当使用自动生成的源文件时,可以添加代码而不需要重新创建源文件。
构造函数中的InitializeComponent()方法反映了窗体设计器中窗体和控件的属性。
7.2窗体另存为
窗体另存为并不是以项目为单位进行储存,而是分为3个文件:
因此必须创建新项目并放入这三个文件。
一种可以达成此目的的方法:
- 新建项目,然后退出,打开保存项目的文件夹;
- 将另存为的三个文件名修改为默认名称(Form1),然后替换文件夹中的同名文件,注意新建项目中,在未操作的情况下,文件夹中没有Form1.resx。
下图为新建项目目录。