艾伟_转载:WinForm二三事(一)

在进入正文之前,想请大家先欣赏下面两段代码:

   1: //这是一个控制台程序,请先添加System.Windows.Form.dll的引用
   2: using System.Windows.Form;
   3:  
   4: public class ConsoleApplicationShowDialog
   5: {
   6:     static void Main()
   7:     {
   8:         Form frm = new Form();
   9:         frm.ShowDialog();
  10:     }
  11: }
   1: //这是一个控制台程序,请先添加System.Windows.Form.dll的引用
   2: using System.Windows.Form;
   3:  
   4: public class ConsoleApplicationShow
   5: {
   6:     static void Main()
   7:     {
   8:         Form frm = new Form();
   9:         frm.Show();
  10:     }
  11: }

两个代码片段都是控制台程序(编译的时候,请选择ConsoleApplication类型编译)。这两段程序唯一的区别就在于显示窗体的时候第一个使用ShowDialog(就是所谓的模态窗体),第二个使用Show(也就是所谓的非模态窗体)。

经过测试我们发现,使用Show显示出来的窗体一显示就死在那里了,不响应用户的输入,如果你在窗体上放一个按钮,甚至发现按钮都无法显示,点击也无任何响应。而是用ShowDialog显示出来的窗体却不一样,可以响应用户的输入。这是什么原因呢?

为了找到问题的根源,我们来看看Show方法和ShowDialog方法实现的区别。Show方法是在Control里定义的,Form间接的派生自Control类(看起来这里是一个组合模式哦),Show方法代码:

   1: public void Show()
   2: {
   3:     this.Visible = true;
   4: }

Show方法的代码相当的简单,做的工作仅仅就是将窗体显示出来,那前面第二段代码应该与下面的代码作用是一样的:

   1: //这是一个控制台程序,请先添加System.Windows.Form.dll的引用
   2: using System.Windows.Form;
   3:  
   4: public class ConsoleApplicationShow
   5: {
   6:     static void Main()
   7:     {
   8:         Form frm = new Form();
   9:         frm.Visible = true;
  10:     }
  11: }

现在再来看看ShowDialog方法,ShowDialog方法有些复杂,但是在这百来行代码中,应该有一条你很熟悉:

   1: public DialogResult ShowDialog(IWin32Window owner)
   2: {
   3:     //...省略
   4:     Application.RunDialog(this);
   5:     //...省略
   6: }

哦,这行代码跟我们千千万万个WinForm程序的启动部分相当类似:

   1: public class Program
   2: {
   3:     static void Main()
   4:     {
   5:         Form frm = new Form();
   6:         Application.Run(frm);
   7:     }
   8: }

MSDN对Application.Run的说明是:

Begins running a standard application message loop on the current thread, and makes the specified form visible.
在当前的线程上启动一个标准的应用程序“消息循环”,并且显示指定的窗体

下面是Application.Run的代码:

public static void Run(Form mainForm)
{
    ThreadContext.FromCurrent().RunMessageLoop(-1, new ApplicationContext(mainForm));
}

哦?什么是消息循环?如果你是直接进入.Net开发的,没有经过Win32时代的洗礼,那可能对这个消息循环并不是很清楚,在你眼里只有注册事件,处理事件。虽然.Net通过封装,简化了消息循环这种处理用户点击等事件的编程模型,但是.Net底下还是Win32,有的时候我们还是得了解一下,对理解有些问题可能有帮助(后面会提到)。

消息循环(Message Loop)

说Application.Run启动一个消息循环,那么什么是消息循环呢?看下面的代码:

MSG msg;
 
while(GetMessage(&msg,NULL,0,0))
{
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

这是一段几乎所有使用Win32 API编写Windows Application的程序里都有的代码。这就是一个消息循环。你不需要透彻的理解上面这段代码,你只需要了解这么一个意思:

Windows为每个Windows程序都维护了一个消息队列,当有用户输入事件的时候,Windows就把这个事件转换为一个称之为“消息”的东东(也就是上面代码中的MSG结构),
在这个消息里包含有一些信息,比如鼠标点击的点啊,消息的类型啊等等。
而上面的while循环中的GetMessage方法就是不断的从这个消息队列里取消息出来,然后处理,这样窗体就能响应用户的输入了。

通过上面的讨论,我们现在大概明白了为啥Show和ShowDialog区别这么大呢,原来ShowDialog启动了一个消息循环,这样用ShowDialog显示出来的窗体就能响应用户的输入事件了,而Show仅仅是设置一下窗体的Visible属性,并没有启动一个消息循环,使用Show显示出来的窗体也就无法响应用户的输入事件了,也就是死在那里了。

上面说,GetMessage取出消息,然后处理,那在哪儿处理呢?在Win32程序中我们还可以看到这样的片段:

LRESULT CALLBACK WndProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)
{
    switch(message)
    {
        case WM_CREATE:
            //处理窗体创建事件
               return 0;
        case WM_PAINT:
            //处理窗体绘制事件
              return 0;
        
        //更多事件,比如按钮点击等
    }
}

啊,好丑陋的处理方式。原来是根据message的类型,做出不同的处理,而Windows定义了一大堆WM_开头的东东。可我们可爱的.Net,WinForm里面优美的事件处理模型就是基于这个之上的,通过上面的代码,和你在.Net里使用事件的感触你是否能想象出.Net是如何封装这个过程的?

WinForm中的消息处理

实际上在.Net的WinForm中,消息处理的影子还是存在的,并没有消失得无影无踪,在Form中还有这么一个protected的方法:

protected override void WndProc(ref Message m)
{
    switch (m.Msg)
    {
        case 0x10://......
        case 0x11:
        //....
    }
    base.WndProc(ref m);
}

哦,原来与Win32里面的那个一模一样。实际上通过重写这个方法我们可以实现一些正常做法难以实现的东东。

为什么耗时操作要异步

谈了这么多,我们来谈一点我们身边的事情。你应该碰到过这样一个场景:编写一个程序,点击一个按钮之后要做一个比较耗时的操作,比如要更新一大批数据到数据库,这个时候程序就像本文开头那个程序一样,死掉了。用户不管怎么点击,程序变成灰色,标题栏上还显示一个“没有响应”,有的程序甚至连个提示都不给,用户以为真的死掉了,气急败坏的啪嚓一下把程序关了,耗时操作进行到一半就这样被无情终止。这是为什么呢?

通过前面的讨论,我们知道,响应用户的输入就是靠消息循环,而消息循环就是在当前的线程上,也就是我们所谓的那个UI线程,如果一个耗时操作也同在UI线程上,那么消息循环就“卡着”了,也就无法处理后续的消息,程序也就假死了。

那我们如何处理这种耗时操作呢?当然就是将这个耗时操作放到另外一个线程中,不占用UI线程,让消息循环得以继续的进行下去。

(未完待续)

时间: 2024-10-22 13:51:30

艾伟_转载:WinForm二三事(一)的相关文章

艾伟_转载:二十行C#代码打造Ruby Markup Builder

从.NET诞生之日起就有了XML类库,但是从使用上来说非常不方便.例如我们需要构造一个XML文档时,使用DOM API就要这样搞: var xmlDoc = new XmlDocument(); var rootEle = xmlDoc.CreateElement("persons"); xmlDoc.AppendChild(rootEle); var person1 = xmlDoc.CreateElement("person"); person1.InnerTe

艾伟_转载:WinForm二三事(二)

监视消息循环 在上一篇文章中,我们讨论了消息循环是响应用户输入的根本,还提到了在WinForm中执行耗时操作是因为这个耗时操作与消息循环在同一个UI Thread上,导致不能处理用户的后续响应,造成程序假死.除此之外,还说到了Form中的WndProc方法,说这个方法就是Win32时代那个处理消息的方法的.Net版. 那么今天这篇文章我们就来编个小程序来模拟一下这个耗时操作,看看是不是如上一篇所说:耗时操作造成消息循环的临时中断不能响应用户后续输入. 程序很简单,就是一个简单的窗体,上面放置一个

艾伟_转载:基于.NET平台的Windows编程实战(二)—— 需求分析与数据库设计

本系列文章导航 基于.NET平台的Windows编程实战(一)--前言 基于.NET平台的Windows编程实战(二)-- 需求分析与数据库设计 基于.NET平台的Windows编程实战(四)-- 数据库操作类的编写 基于.NET平台的Windows编程实战(五)-- 问卷管理功能的实现 基于.NET平台的Windows编程实战(六)-- 题目管理功能的实现 大家都知道一个系统的成败与否关键在于其所做的需求分析是否到位,数据库的设计是否合理.因为本系列文章的目的是在于提高大家对.NET Wind

艾伟_转载:C# WinForm开发系列 - TextBox

包含金额/日期输入框,带弹出数字面板的计算输入框,安全密码输入等控件(文章及相关代码搜集自网络,仅供参考学习,版权属于原作者! ).   1.CalculatorBox    CalculatorBox.rar 2.带行号+自定义颜色显示的TextBox 3.金额输入框   currency_textbox.zip   CurrencyBox.rar   NumberPicker_src.zip   NumericTextBox_src.zip   NumberedTextbox.rar 4.日

艾伟_转载:.NET Discovery 系列之二--string从入门到精通(勘误版下)

本系列文章导航 .NET Discovery 系列之一--string从入门到精通(上) .NET Discovery 系列之二--string从入门到精通(勘误版下) .NET Discovery 系列之三--深入理解.NET垃圾收集机制(上) .NET Discovery 系列之四--深入理解.NET垃圾收集机制(下) .Net Discovery 系列之五--Me JIT(上) .NET Discovery 系列之六--Me JIT(下) .NET Discovery 系列之七--深入理解

艾伟_转载:Cookie是什么?用法是怎样?与SESSION有什么区别?(二)

二session 简介 IE中: 有效的窗品包括 1.Session对象只在建立Session对象的窗口中有效. 2.在建立Session对象的窗口中新开链接的窗口 无效的窗口包括 1.直接启动IE浏览器的窗口 2.不是在建立Session对象的窗口中新开链接的窗口 NetScape中: 只要一个窗口有了某个Session对象,则全部窗口对此Session都有效 Session是什么呢?简单来说就是服务器给客户端的一个编号.当一台WWW服务器运行时,可能有若干个用户浏览正在运正在这台服务器上的网

艾伟_转载:LINQ to SQL、NHibernate比较(二)-- LINQ to SQL实例

    用ADO.NET操作数据库大家一定再熟悉不过了,select.insert.update等等SQL语句大家也都必然滚瓜烂熟.我将自己在学习LINQ to SQL过程中的动手经历记录下来,作为今后学习的参考,也希望对刚刚接触的人有一点帮助.     我在本文涉及到一个很简单的系统,利用DataGridView实现数据库数据的批量增.删.改,不是什么强大的功能.     如果有人感兴趣,可以在看完我的这篇文章之后用ADO.NET实现同样的功能,看看到底会比使用LINQ to SQL多多少时间

艾伟_转载:WinForm界面开发之酒店管理系统--开篇

星移斗转,时光似箭,不知不觉中,酒店管理系统的开发从开始到现在的结束,已经2个月了,2个月的业余时间,2个月的生活情趣,都寄托在这个软件当中,经历了各种艰苦和困惑,终于得以修成正果---深田之星酒店管理系统的顺利发布. 技术的历程是一个开拓进取.攻克难题的历程,其中有困惑也有兴奋,有苦涩也有甜蜜, 在这个过程中,再一次检阅了我的Database2Sharp代码自动生成的开发工具的,再一次从"深田之星送水管理系统"进行升华,技术从来没有尽头,只有不断完善,以及不断的超越和创新.在这个过程

艾伟_转载:.NET Discovery 系列之七--深入理解.NET垃圾收集机制(拾贝篇)

本系列文章导航 .NET Discovery 系列之一--string从入门到精通(上) .NET Discovery 系列之二--string从入门到精通(勘误版下) .NET Discovery 系列之三--深入理解.NET垃圾收集机制(上) .NET Discovery 系列之四--深入理解.NET垃圾收集机制(下) .Net Discovery 系列之五--Me JIT(上) .NET Discovery 系列之六--Me JIT(下) .NET Discovery 系列之七--深入理解