本章的前面讨论了如何创建简单的Windows应用程序。该示例包含一个派生于System. Windows.Forms.Form的类。根据.NET Framework说明文档,"窗体是应用程序中窗口的表示方式。"如果您具有Visual Basic背景,就会很熟悉术语"窗体"。如果您是使用MFC的C++程序员,就可能习惯把窗体称为窗口、对话框或框架。无论怎样,窗体都是与用户交互的基本方式。我们已经介绍了Control类中一些较常见的属性、方法和事件,而且Form类派生于Control类,所以在Form类中存在这些属性、方法和事件。Form类还在Control类的基础上添加了大量的功能,本节就介绍这些功能。
Windows客户应用程序可以包含一个窗体或上百个窗体。它们可以是基于SDI(单文档界面,Single Document Interface)或MDI(多文档界面,Multiple Document Interface)的应用程序。但无论怎样,System.Windows.Forms.Form类都是Windows客户应用程序的核心。Form类派生于ContainerControl,ContainerControl又派生于ScrollableControl,ScrollableControl则派生于Control类。因此可以假定,窗体可以是其他控件的容器,当所包含的控件在客户区域中显示不下时可以滚动显示,窗体可以拥有与其他控件相同的属性、方法和事件。所以,Form类相当复杂。本节就介绍这些功能。
理解创建窗体的过程是很重要的。我们要完成的工作取决于编写初始化代码的位置。对于实例,事件以如下顺序发生:
● 构造函数
● Load
● Activated
● Closing
● Closed
● Deactivate
前3个事件在初始化过程中发生。根据初始化的类型,可以确定要关联哪个事件。这个类的构造函数在对象的实例化过程中执行。Load事件在对象实例化后,窗体可见之前发生。它与构造函数的区别是窗体的可见性。在引发Load事件时,窗体已存在,但不可见。在构造函数的执行过程中,窗体还不存在,处在实例化过程中。Activated事件在窗体处于可见状态并处于当前状态时发生。
有一种情形会略微改动一下这个事件执行顺序。如果在窗体构造函数执行的过程中,Visible属性设置为true,或调用了Show方法(它把Visible属性设置为true),就会立即引发Load事件。这也会使窗体可见,并处于当前状态,所以还会引发Activate事件。如果在设置Visible属性后还有代码,就执行这些代码。所以启动事件的执行顺序如下所示:
● 构造函数,执行到Visible = true为止
● Load
● Activate
● 构造函数,执行Visible = true之后的代码
这会产生一些未预料到的结果。最好在构造函数中进行尽可能多的初始化。
关闭窗体时会发生什么情况 Closing事件可以取消处理,它把CancelEventArgs作为一个参数,如果把Cancel属性设置为true,就会取消该事件,窗体仍处于打开状态。Closing事件在窗体关闭时发生,而Closed事件在窗体关闭后发生。这两个事件都允许执行必要的清理工作。注意,Deactivate事件在窗体关闭后发生,这是另一个可能产生难以查找的错误的来源。确保不在Deactivate事件中执行防止窗体被正常垃圾收集的操作。例如,设置对另一个对象的引用会使窗体仍未被释放。
如果调用Application.Exit()方法,且当前有一个或多个窗体处于打开状态,就不会引发Closing和Closed事件。如果打开了正要清理的文件或数据库连接,这就是一个需要考虑的问题,此时应调用Dispose方法,所以另一种更好的方法是把大多数清理代码放在Dispose方法中。
与窗体启动相关的一些属性有StartPosition、ShowInTaskbar和TopMost。StartPosition可以是FormStartPosition枚举中的一个值:
● CenterParent:窗体位于父窗体的客户区域中心。
● CenterScreen:窗体位于当前屏幕的中心。
● Manual:窗体的位置根据Location属性的值来确定。
● WindowsDefaultBounds:窗体位于默认的Windows位置,使用默认的大小。
● WindowsDefaultLocation:窗体位于默认的Windows位置,但其大小根据Size属性来定。
ShowInTaskbar属性确定窗体是否应在任务栏上可见。如果窗体是一个子窗体,且只希望父窗体显示在任务栏上时,才使用这个属性。TopMost属性指定窗体在应用程序启动时位于最上面,即使窗体没有立即获得焦点,也位于最上面。
为了让用户与应用程序交互,用户必须能看到窗体。利用Show和ShowDialog方法就可以实现这一点。Show方法仅使窗体对用户可见。下面的代码演示了如何创建一个窗体,并把它显示给用户。假定要显示的窗体叫做MyFormClass:
MyFormClass myForm = new MyFormClass(); |
这是非常简单的。但它的一个缺点是没有给调用代码发送任何通知,说明MyForm已处理完,并退出。有时这并不重要,Show方法工作得很好。如果需要提供某种通知,使用ShowDialog方法是一种比较好的选择。
在调用Show方法后,Show方法后面的代码会立即执行。在调用ShowDialog方法后,调用代码被暂停执行,等到调用ShowDialog方法的窗体关闭后再继续执行。不仅调用代码被暂停执行,而且窗体也可以返回一个DialogResult值。DialogResult枚举是一组标识符,它们描述了对话框关闭的原因,包括OK、Cancel、Yes、No和其他几个标识符。为了让窗体返回一个DialogResult值,必须设置窗体的DialogResult属性,或者在窗体的一个按钮上设置DialogResult属性。
例如,假定应用程序的一部分要求提供客户的电话号码。窗体包含一个输入电话号码的文本框,和两个按钮OK和Cancel。如果把OK按钮的DialogResult属性设置为DialogResult.OK,把Cancel按钮的DialogResult属性设置为DialogResult.Cancel,则在选择其中一个按钮时,窗体就会不可见,并给调用它的窗体返回相应的DialogResult值。现在注意窗体没有释放,只是把Visible属性设置为false。这是因为仍必须从窗体中获取值。在这个示例中,我们需要电话号码。在窗体上为电话号码创建一个属性,这样父窗体就可以获取值,并调用窗体上的Close方法了。下面就是子窗体的代码:
namespace FormsSample.DialogSample btnOK.DialogResult = DialogResult.OK; }
|
首先要注意,不包含处理按钮单击事件的代码。因为设置了每个按钮的DialogResult属性,所以在单击OK或Cancel按钮后,窗体就消失了。添加的唯一属性是PhoneNumber。下面的代码显示了父窗体中调用Phone对话框的方法:
Phone frm = new Phone(); frm.ShowDialog(); frm.Close(); |
这看起来非常简单。创建新的Phone对象frm,在调用frm.ShowDialog()方法时,这个方法中的代码会停止执行,等待Phone窗体返回。接着检查Phone窗体的DialogResult属性。由于窗体还未释放,是不可见的,所以仍可以访问公共属性,其中一个公共属性就是PhoneNumber。一旦获取了需要的数据,就可以调用窗体的Close方法。