第十二章 Winform 框架

目录

1 Win32 应用程序结构

1.1 Windows 消息循环

1.2 窗口过程

1.3 创建基于 Win32 的单窗体应用程序

1.4 创建基于 Win32 的多窗体应用程序

2 .NET Winform 程序与传统 Win32 程序的关联

3 Windows Forms 框架

4 Winform 程序结构

4.1 UI线程

4.2 消息循环

4.3 窗口过程

5 模式窗体与非模式窗体


1 Win32 应用程序结构

1.1 Windows 消息循环

Windows 消息循环结构类似下图:

第十二章 Winform 框架_第1张图片

C++ 中 Windows 消息循环代码类似如下:

MSG msg; // 代表一条消息
BOOL bRet;

// 从UI线程消息队列中取出一条消息
while ((bRet = GetMessage(&msg,NULL,0,0)) != 0)
{
    if (bRet == -1)
    {
        // 错误处理代码,通常是直接退出程序
    }
    else
    {
        TranslateMessage(&msg);   // 转换消息格式
        DispatchMessage(&msg);    // 分发消息给相应的窗体
    }
}

上图显示了 Windows 消息循环的结构,GetMessage 从操作系统中的消息队列(一种数据结构)获取 Windows 消息,(这些消息包括操作系统将用户输入转换而成的、操作系统本身发送给程序的以及其它程序发送给本程序的)。如果获取到的消息不为 WM_QUIT(表示退出含义),那么经过 TranslateMessage 方法进行预处理后,再由 DispatchMessage 将消息投递到指定的窗口过程(WndProc),窗口过程则负责处理消息。

注:GetMessage 方法有三种返回值,当取得 WM_QUIT 消息时,返回 0 值,While 循环退出;当有异常发生时,返回 -1 值;当获取非 WM_QUIT 的其它消息时,返回非 0 非 -1 的值。如果一个程序需要退出,那么 Windows 消息循环所在的线程必须结束,也就是说,While 循环中的 GetMessage 方法必须返回 0 值,由于 WM_QUIT 时 While 循环退出的条件,因此,消息队列中包含有一个 WM_QUIT 消息是程序退出的前提。

C++中 Windows 消息结构体的定义如下:

typedef struct tagMSG {
   HWND        hwnd;
   UINT        message;
   WPARAM      wParam;
   LPARAM      lParam;
   DWORD       time;
   POINT       pt;
} MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;

如上代码所示,消息中包含有消息接收者的窗口句柄 hwnd,消息类型 message(一个数值,代码中常用 WM_PAINT、WM_QUIT 等代表),以及两个消息参数 wParam 和 IParam,还有发送消息时间 time 以及当前光标在屏幕中的坐标位置。消息投递方法 DispatchMessage 就是根据 hwnd 字段,将消息投递给指定窗口的窗口过程,最终由窗口过程处理这一消息。

1.2 窗口过程

消息处理的关键是 DispatchMessage 函数。这个函数根据取出的消息中所包含的窗体句柄,将这一消息转发给此句柄所对应的窗体对象。
而窗体负责响应消息的函数称为 “窗体过程(Window Procedure)”。

窗口过程用 C++ 代码声明类似如下:

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
   int wmId, wmEvent;
   switch (message)
   {
      case WM_COMMAND:
         wmId = LOWORD(wParam); // 发送消息的控件或菜单的ID
         wmEvent = HIWORD(wParam); // 通知消息
         // 分析菜单选择
         switch (wmId)
         {
            case IDM_ABOUT:
               DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
               break;
            case IDM_EXIT:
               DestroyWindow(hWnd);
               break;
            default:
               return DefWindowProc(hWnd, message, wParam, lParam);
         }
         break;
      case WM_PAINT:
         // draw something here with gdi win32 api
         break;
      case WM_DESTROY:
         PostQuitMessage(0);   // 给消息队列发送一个 WM_QUIT 消息,下一次消息循环时,GetMessage 从操作系统中的消息队列中取出 WM_QUIT 后,While 消息循环就会结束。
         break;
      default:
         return DefWindowProc(hWnd, message, wParam, lParam);
   }
   return 0;
}

注:如果窗口为一个程序的主窗体,那么当这个窗口关闭后,程序应该退出(While 循环结束),又因为当关闭窗体后,系统会有一个 WM_DESTROY 消息发送给窗口过程(注意是直接发送给窗口过程),那我们完全可以在 switch/case 中拦截 WM_DESTROY 消息,然后调用 PostQuitMessage 这个 Win32 API 处理。

在 Windows 中,消息分成了两类:

1、队列消息:需要存入消息队列,然后由消息循环取出来之后才能被窗口过程处理。例如:WM_PAINT、WM_QUIT

2、非队列消息:不需要存入消息队列,也不经过消息循环,它们直接传递给窗口过程,由窗口过程直接处理。例如:WM_DESTROY

注:可以用 PostMessage() 或 SendMessage() 发送消息。PostMessage() 把一个消息放入消息队列后立即返回,也就是当调用 PostMessage(),函数执行完成返回时,很可能消息尚未处理。SendMessage() 直接将消息发送到窗口,直到这个消息处理完成才返回。如果要关闭一个窗口,可以给它发送一个 WM_CLOSE 消息,像PostMessage(hwnd,WM_CLOSE,0,0); 效果跟点击窗口右上角的关闭按钮是一样的。注意这里的 wParam 和 IParam 的值都是0,因为 WM_CLOSE 消息会忽略上述两个参数。

1.3 创建基于 Win32 的单窗体应用程序

int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow)    //NO.1 程序入口方法
{
   WNDCLASSEX wcex;
   wcex.style = CS_HREDRAW | CS_VREDRAW;
   if (!RegisterClassEx(&wcex))      //NO.2 注册窗体类
   {
      MessageBox(NULL,_T("Call to RegisterClassEx failed!"),_T("Win32 Guided Tour"),NULL);
      return 1;
   }
   hInst = hInstance;    // Store instance handle in our global variable
   HWND hWnd = CreateWindow(szWindowClass,szTitle);     //NO.3 创建一个窗体
   if (!hWnd)
   {
      MessageBox(NULL,_T("Call to CreateWindow failed!"),_T("Win32 Guided Tour"),NULL);
      return 1;
   }
   ShowWindow(hWnd,nCmdShow);     //NO.4 显示窗体
   UpdateWindow(hWnd);    //NO.5 更新窗体
   MSG msg;
   while (GetMessage(&msg, NULL, 0, 0))    //NO.6 消息循环
   {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
   }
   return (int) msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)  //NO.7 窗口过程函数
{
   PAINTSTRUCT ps;
   HDC hdc;
   TCHAR greeting[] = _T("Hello, World!");
   switch (message)
   {
      case WM_PAINT:    //NO.8 将字符串显示到窗体界面
         hdc = BeginPaint(hWnd, &ps);
         TextOut(hdc,5, 5,greeting, _tcslen(greeting));
         EndPaint(hWnd, &ps);
         break;
      case WM_DESTROY:    //NO.9 向消息队列发送一个 WM_QUIT 消息
         PostQuitMessage(0);
         break;
      default:
         return DefWindowProc(hWnd, message, wParam, lParam);    //NO.10 系统默认API
         break;
   }
   return 0;
}

注:注意窗口过程的调用不是显示的,也就是说,我们在代码中根本看不见调用窗口过程的代码,在需要处理消息(无论是队列消息还是非队列消息)时,系统会自动以消息作为参数,调用对应的窗口过程,而这些是不受开发者控制的,正因为如此,整个程序的运行流程不是特别明了,需要仔细揣摩,下面一张图解释了本小节实例代码的完整运行流程:

第十二章 Winform 框架_第2张图片

我们在创建窗体时(CreateWindow 还未返回),会发送类似 WM_CREATE 这样的非队列消息给新创建出来的窗体,告诉窗体: 我们已经创建好了,同理,显示窗体等其它操作,也有可能给窗体发送类似 WM_ACTIVATE 这样的非队列消息,告诉窗体: 我们已经激活了,这些过程都会调用对应窗体的窗口过程。进入消息循环后,消息循环不断地从消息队列中获取队列消息,然后由 DispatchMessage 方法投递给对应的窗口过程,在窗口过程中,我们处理了 WM_PAINT 消息,将字符串输出到窗体界面。注意图中 NO.1 处的箭头打了一个“叉”,因为下面的 WndProc 不一定只在 DispatchMessage 后面才调用,在其它的地方也有可能被调用,比如处理非队列消息的时候。程序运行效果图如下:

第十二章 Winform 框架_第3张图片

1.4 创建基于 Win32 的多窗体应用程序

上一小节中,我们是在消息循环(While 循环)之前创建的主窗体,其实可以在任何地方使用 CreateWindow 创建窗体,下面示例代码演示怎么在主窗体的鼠标右键按下消息中创建另外一个新窗体。

TCHAR szTitle[MAX_LOADSTRING]; // 标题栏文本 Win32 Message Loop
TCHAR szWindowClass[MAX_LOADSTRING]; // 主窗口类名
TCHAR szWindowClass_child[] = _T("Win32_APP_Child"); // 子窗口类名
LRESULT CALLBACK WndProc2(HWND,UINT,WPARAM,LPARAM);   // NO.1

int APIENTRY _tWinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPTSTR lpCmdLine,int nCmdShow)     // NO.2
{
   MyRegisterClass(hInstance);  // NO.3|4|5|6
   // 执行应用程序初始化:
   if (!InitInstance (hInstance, nCmdShow))   // NO.7|8|9
   {
      return FALSE;
   }
   // 消息循环:
   while (GetMessage(&msg, NULL, 0, 0))   // NO.10
   {
      if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
      {
         TranslateMessage(&msg);
         DispatchMessage(&msg);
      }
   }
   return (int) msg.wParam;
}

ATOM MyRegisterClass(HINSTANCE hInstance)
{
   WNDCLASSEX wcex;
   wcex.lpfnWndProc= WndProc;// NO.3
   wcex.lpszClassName= szWindowClass;
   RegisterClassEx(&wcex);// NO.4
   wcex.lpfnWndProc = WndProc2;// NO.5
   wcex.lpszClassName = szWindowClass_child;
   return RegisterClassEx(&wcex);// NO.6 注册窗体2
}

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   HWND hWnd;
   hInst = hInstance; // 将实例句柄存储在全局变量中
   nCmdS = nCmdShow;
   hWnd = CreateWindow(szWindowClass, szTitle, hInstance);// NO.7
   ShowWindow(hWnd, nCmdShow);  // NO.8
   UpdateWindow(hWnd);   // NO.9
   return TRUE;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)   // NO.11
{
   TCHAR greeting[] = _T("Hello,World!");
   HWND hWnd2 = 0;
   switch (message)
   {
      case WM_PAINT:    // NO.12
         hdc = BeginPaint(hWnd, &ps);
         // TODO: 在此添加任意绘图代码
         TextOut(hdc,5,5,greeting,_tcslen(greeting));
         EndPaint(hWnd, &ps);
         break;
      case WM_RBUTTONDOWN:    // NO.13
         hWnd2 = CreateWindow(szWindowClass_child, szTitle, hInst);   // NO.14 创建窗体2
         ShowWindow(hWnd2,nCmdS);   // NO.15
         UpdateWindow(hWnd2);   // NO.16
         break;
      case WM_DESTROY:// NO.17
         PostQuitMessage(0);
         break;
      default:
         return DefWindowProc(hWnd, message, wParam, lParam);
   }
   return 0;
}

LRESULT CALLBACK WndProc2(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)   // NO.19 窗体2的过程函数
{
   TCHAR greeting[] = _T("Hello,World! I am Child Window");
   switch (message)
   {
      case WM_PAINT:   // NO.20
         hdc = BeginPaint(hWnd, &ps);
         // TODO: 在此添加任意绘图代码
         TextOut(hdc,5,5,greeting,_tcslen(greeting));
         EndPaint(hWnd, &ps);
         break;
      default:
         return DefWindowProc(hWnd, message, wParam, lParam);
   }
   return 0;
}

无论是主窗体还是子窗体,都共用一个消息循环,整个示例代码的运行流程如下:

第十二章 Winform 框架_第4张图片

无论新创建出来的窗体有多少个,都是共用同一个消息循环,DispatchMessage 会根据窗口句柄,将消息投递到对应的窗口过程。运行效果图如下:

第十二章 Winform 框架_第5张图片

注:一些 WIN32 API 在调用过程中,会给它操作的窗体发送非队列消息,比如 CreateWindow 和 ShowWindow 等 API。

调用 CreateWindow 创建窗体时会向新创建的窗体发送以下非队列消息 : WM_GETMINMAXINFO 、 WM_NCCREATE 、WM_NCCALCSIZE、 WM_CREATE;

调用 ShowWindow 显示窗体时,它会向显示的窗体发送以下非队列消息:WM_SHOWWINDOW、WM_WINDOWPOSCHANGING、WM_WINDOWPOSCHANGING 、 WM_ACTIVATEAPP 、 WM_NCACTIVATE 、WM_GETTEXT、 WM_ACTIVATE、 WM_IME_SETCONTEXT、 WM_IME_NOTIFY、WM_SETFOCUS 、 WM_NCPAINT 、 WM_GETTEXT 、 WM_ERASEBKGND 、WM_WINDOWPOSCHANGED、 WM_SIZE、 WM_MOVE,由此可见,窗体的窗口过程几乎无时无刻都在被调用。

2 .NET Winform 程序与传统 Win32 程序的关联

Winform 应用程序是 .NET 平台中使用 Windows Forms 框架开发出来的 Windows 桌面应用程序,它属于一种托管程序,必须运行在 .NET 平台中。类似 MFC,Windows Forms 框架内部也是通过原始 Win32 API 实现的那么,Winform 应用程序和传统 Win32 应用程序有哪些相同点和不同点呢?

相同点:

(1)两者都是运行在 Windows 平台中的 Windows 桌面应用程序;

(2)两者都是通过(直接/间接)调用 Win32 API 来实现完成的。

不同点:

(1)前者属于托管程序,需要 .NET 平台支持,为托管时代的产物,后者属于非托管程序;

(2)前者使用 .NET Frameworks(Windows Forms 部分)框架开发完成,间接地调用 Win32 API,后者直接使用 Win32 API 开发完成。

3 Windows Forms 框架

Windows Forms 是 .NET 框架中的一个分支,主要用于桌面应用程序的开发,编程中常用类型包含在 System.Windows.Forms.dll 程序集中的 System.Windows.Form 命名空间中。

在传统 Win32 开发模式中,窗体数据、操作窗体的方法以及窗体的窗口过程等都是相互独立的,没有面向对象的概念。如果一个应用程序只考虑有一个 UI 线程,那么传统 Win32 开发模式中,程序的数据结构类似如下图1所示:

第十二章 Winform 框架_第6张图片

图1 传统 Win32 程序结构

而在使用 Windows Forms 框架开发的 Winform 程序中,窗体(控件)的数据、方法等都是以一个整体出现的(类对象),如果一个 Winform 应用程序只考虑有一个 UI 线程,Winform程序的数据结构如图2所示:

第十二章 Winform 框架_第7张图片

图2 Winform 程序结构

如上图2,在 Windows Forms 框架中,以 Control 为基类, 其它所有与窗体显示有关的控件几乎都派生自它,Control 类中的 WndProc 虚方法就是我们在 Win32 开发模式中所熟悉的窗口过程。

public class Control
{
   protected virtual void WndProc(ref Message m);
}

控件及窗体都派生自 Control 类,并对 WndProc 方法进行了重写。

public class Button : Control
{
   protected override void WndProc(ref Message m);
}

public class Form : Control
{
   protected override void WndProc(ref Message m);
}

Windows Forms 框架已经帮我们完成了消息循环,在我们开发过程中,不再需要我们自己去编写消息循环。同时在大多数情况下,我们并不需要去接触 Control(或其派生类)类中的 WndProc 这个方法。因为在 Control 类的 WndProc 方法中已经做了默认处理,它将Windows 消息以及消息携带的参数直接转换成了.NET 中的事件和事件参数,当消息到达时,Control 类对象会激发与该消息对应的事件,从而通知事件注册者。当然必要时,我们完全可以在 Control 类的派生类中重写 WndProc,它包含一个 Message 结构体类型的参数,与 Win32 开发中的 Msg 结构体类似,表示一个 Windows 消息,重写 WndProc 虚方法时,我们可以从源头接触到 Windows 消息。

使用 Windows Forms 框架开发程序时,开发者主要负责的部分如下图所示:

第十二章 Winform 框架_第8张图片

如上图所示,虚线框为开发者负责部分,可以看出,我们编写的代码,都是被窗口过程WndProc 回调。需要注意图中 NO.1 处的箭头有一个“叉”,因为控件的窗口过程并不止在控
件处理队列消息的时候调用,还可能在处理非队列消息的时候调用 。

System.Windows.Forms 命名空间中的类型主要分以下几类:
1) 控件,以 Control 类为基类,主要负责界面显示;
2) 委托,控件激发事件的委托类型;
3) 事件参数,控件激发事件时传递的参数;
4) 枚举,跟控件显示有关的属性值;
其它功能类,比如 Application 类,控制整个 Winform 程序。

4 Winform 程序结构

在.NET 中,使用 Windows Forms 框架开发的 Windows 桌面应用程序称为 Winform 应用程序。

4.1 UI线程

在每个 Winform 程序的 Program.cs 文件中,都有一个 Main 方法,该 Main 方法就是程序的入口方法,每个程序启动时都会以 Main 方法为入口,创建一个线程,这个线程就是 UI 线程,可能你会问 UI 线程怎么没有消息循环呢?那是因为 Main 方法中总是会出现一个类似Application.Run 的方法,而消息循环隐藏在了该方法内部,具体参见下一小节。一个程序理论上可以有多个 UI 线程,每个线程有自己的消息队列(由操作系统维护)、消息循环、窗体等。下图显示了包含多个 UI 线程的应用程序:

第十二章 Winform 框架_第9张图片

如上图所示,每个 UI 线程都有自己的消息队列、消息循环以及自己创建的控件,相互之间不干扰,只有程序中所有的 UI 线程结束以后,整个程序才会结束(Exit)。由于 UI 线程之间的数据交换比较复杂,因此在实际开发中,在没有特殊需求下,一个程序一般只包含有一个UI 线程。

Winform 程序中, Application 类代表一个程序,该类几乎只包含静态成员,类似全局成员
的作用,给整个应用程序提供帮助。

4.2 消息循环

Program.cs 文件里 Main 方法中 Application.Run 方法中包含了消息循环。

static class Program
{
   [STAThread]
   static void Main()
   {
       Application.Run(new Form1());
   }
}

1)一个程序可以包含多个 UI 线程,我们完全可以通过 System.Threading.Thread 类新创建一个普通线程,然后在该线程中调用 Application.Run 方法来开启消息循环;

2)一个 UI 线程结束(该线程中的消息循环个数为 0)后,将会激发Application.ThreadExit 事件,告知有 UI 线程结束,只有当所有 UI 线程都结束(程序中消息循环总数为 0),才会激发 Application.Exit 事件,告知应用程序退出。再看一张图,说明 Windows Forms 中消息循环的结构 。

第十二章 Winform 框架_第10张图片

如上图所示,图中显示了包含两个 UI 线程的 Winform 应用程序,虚线框仍然代表开发者负责的部分。可以看到,两个 UI 线程中的消息循环相互独立,互不影响,只有两个 UI 线程都结束了以后,整个应用程序才会退出。

在 Windows Forms 框架的这个单层消息循环内部,经常会出现嵌套循环,比如图中 NO.2 或者 NO.3 处,开发人员完全可以在 btn_Click 事件处理程序中再开启一个消息循环,嵌套消息循环多数出现在 “模态对话框” 中。 如果出现了嵌套消息循环,内外(或者更多个)两个消息循环是公用同一个消息队列的,这符合一个 UI 线程只有一个消息队列的规则。

4.3 窗口过程

Control(或其派生类)类中的 WndProc 虚方法就是控件的窗口过程,之所以将窗口过程声明为虚方法,这是因为 Windows Forms 框架是面向对象的,充分地利用了面向对象编程中的 “多态” 特性,所有 Control 类的派生类均可以重写它的窗口过程,从而从源头上拦截到 Windows 消息,处理自己想要处理的 Windows 消息, Control 类中的 WndProc 虚方法类似如下:

public class Control
{
   public event EventHandler HandleCreated;     //NO.1 定义事件
   public event EventHandler GotFocus;         //NO.2 定义事件
   protected virtual void WndProc(ref Message m)
   {
       switch (m.Msg)      // NO.3
       {
           case WM_CREATE:
               OnHandleCreated(new EventArgs());    //NO.4
               break;
           case WM_SETFOCUS:
               OnGotFocus(new EventArgs());     //NO.5
               break;
           default:
               this.DefWndProc(ref m);        //NO.6
       }
   }

   protected virtual void OnHandleCreated(EventArgs e)       //NO.7
   {
       if (HandleCreated != null)
       {
           HandleCreated(this, e);   // 激发事件
       }
   }

   protected virtual void OnGotFocus(EventArgs e) //NO.8
   {
       if (GotFocus != null)
       {
           GotFocus(this, e);      // 激发事件
       }
   }
}

如上,窗口过程做了一个非常重要的事:将 Windows 消息转换成了 .NET 中的事件。Windows 消息经由窗口过程 WndProc 后,以事件的形式告知事件注册者 。通过这种方法,去调用开发者编写的事件处理程序。

NO.3 处当某个消息成立时,并没有马上激发事件,而是调用了预先定义的一个虚方法 NO.4 与 NO.5。在该虚方法内部激发事件(NO.7与NO.8)。之所以要把激发事件的代码放在一个单独的虚方法中,这是为了让从该类型(Control)派生出来的子类能够重写虚方法,可以重新定义激发事件的逻辑。

虚方法的命名一般为“On+事件名”,另外该虚方法必须定义为 protected,因为派生类中很可能要调用基类的虚方法。

Control 类中的 WndProc 虚方法只处理了一部分 Windows 消息(示例代码不全),其余的都交给了默认窗口过程 DefWndProc 去处理,如果 Control 类的派生类需要自己处理 Control 类中没被处理的 Windows 消息的话,那么派生类就需要重写 WndProc 虚方法,这正是面向对象。
 

const int WM_SYSCOMMAND = 0x112;
const int SC_CLOSE = 0xF060;        // 主窗体关闭按钮事件
protected override void WndProc(ref Message m)      
{
   if (m.Msg == WM_SYSCOMMAND)
   {
       if (m.WParam.ToInt32() == SC_CLOSE)
       {

       }
   }  
    // 重写 WndProc 时,一定不要忘记调用基类的WndProc(base.WndProc),这也是虚方法必须声明为 protected 的原因
   base.WndProc(ref m);
}

5 模式窗体与非模式窗体

Form.Show(); // 显示一个非模式窗体
Form.ShowDialog(); // 显示一个模式窗体

使用 Form.ShowDialog() 显示一个模式窗体时,内部会创建一个嵌套消息循环,只有等内层消息循环退出之后,外层消息循环才能继续运行。内层消息循环与外层消息循环共用同一个消息队列,能为当前线程中所有的控件提供服务。

那么,内层消息循环退出的条件是什么呢? 还是当消息队列中有 WM_QUIT 消息时退出吗?显然不是,因为 WM_QUIT消息是主消息循环(最外层循环)退出的条件,当一个线程的消息队列中有 WM_QUIT 消息时,代表这个 UI 线程将要结束。

内层消息循环退出条件是 Form.DialogResult 为非 DialogResult.None,所以当我们需要关闭模式窗体时,只需要将它的 DialogResult 属性设置为非 DialogResult.None。

public DialogResult ShowDialog();
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{

}

通过将模式窗体的 DialogResult 属性值设置为非 DialogResult.None 之后,内层消息循环退出,模式窗体隐藏,但并没有 Close,因此之后我们还可以继续将该模式窗体显示出来,如果之后不再使用,我们一般需要将模式窗体放在 using 块中,如下代码:

using (Form f = new Form())
{
   f.ShowDialog();
}

如上代码所示,我们将创建模式窗体的代码放在 using 块中,当 f.ShowDialog() 返回后,结束 using 块时,会自动调用 f 的 Dispose 方法去释放非托管资源。


“嵌套消息循环” 指的是什么?
在一次消息循环过程中,再次开启一个消息循环,内部循环退出之前,外部循环一直处于等待状态,但是由于两个循环均负责处理同一个消息队列中的消息,因此不会出现 Windows 消息不能及时被处理的情况。

 

你可能感兴趣的:(.NET,开发要点精讲)