4.4.1.2.打开无模式窗体
要打开一个无模式窗体,可以调用Show()。无模式窗体与有模式窗体的区别是,用户可以在无模式窗体和其他窗体之间切换。这样,用户就可以同时工作于一个应用程序的几个部分。下面的代码演示了怎样动态创建一个无模式的窗体:

上述代码同时演示了怎样防止一个窗体的多个实例存在。记住,无模式的窗体允许用户与应用程序的其他部分交互。这样,用户可以照常使用菜单命令,或者创建TModeless的另一个实例。因此,需要考虑这些实例的创建和删除问题。
要特别注意窗体的实例:当通过窗体的系统菜单或者窗体上的Close按钮关闭这个窗体时,窗体并没有真正从内存中释放。它仍然还在内存中,除非关闭了主窗体(即应用程序)。在上面这个程序示例中,then后面的语句只会执行一次,前提是这个窗体不是自动创建的。如果希望用户关闭了窗体就在内存中释放它,必须处理它的OnClose事件,并且把Action参数设为caFree,这样,VCL就会在这个窗体关闭时释放它。

上述代码解决了窗体实例在内存释放的问题。不过,还有一个问题,注意下面这行代码:

这行代码检查TModeless的实例是否已经由Modeless变量引用,这实际上就是检查Modeless是否为nil。尽管第一次进入例程的时候,Modeless可能是nil,但第二次进入这个例程的时候,它已经不是nil。这是因为VCL并没有把Modeless变量设为nil。因此,必须手工把这个变量设为nil。
与模式窗体不同的是,无法在代码中判断无模式窗体什么时候将删除。因此,无法在创建窗体实例的例程中删除窗体的实例。用户有可能在应用程序正在运行的任何时候关闭无模式窗体。因此,无模式窗体本身一定要把Modeless变量设为nil,而且最好在处理窗体的OnDestroy事件的处理过程中设置这个变量:

这样就能保证每次关闭窗体时,Modeless变量总是被设为nil,从而防止Assigned()函数失败。记住,同一时刻只能创建TModeless的一个实例。
注意:对于无模式窗体来说,要避免出现下列有缺陷的代码:

上述代码会导致每次都创建窗体的实例,重复了被Form1引用的以前的实例,从而消耗了大量的内存。尽管通过Screen.Forms可以访问这些实例,但最好还是尽量避免使用上述代码。向构造器Create()传递nil造成无法在Form1实例变量被覆盖后,无法再引用这个窗体实例指针。

4.4.1.3.管理窗体的图标和边框
TForm有一个BorderIcons属性,它是一个集合,包含下列元素:biSystemMenu、biMinimize、biMaximize和biHelp。只要让这个集合不包含其中的某个元素,就可以使窗体上不出现相应的系统菜单、最大化按钮和最小化按钮,但窗体上总是有关闭按钮。
还可以通过BorderStyle属性改变窗体的非客户区。BorderStyle属性的定义如下:

BorderStyle属性可以使窗体具有下列特征:
1. bsDialog不能重设大小,只有关闭按钮。
2. bsNone没有边框,不能重设大小,没有按钮。
3. bsSingle不能重设大小,有所有按钮。如果biMinimize和biMaximize中只有一个按钮被设为False,那么窗体上有两个按钮。但设为False的按钮不可用。如果二者均为False,那么没有按钮在窗体上显示。如果biSystemMenu,则没有按钮显示。
4. bsSizeable有边框,有所有按钮,按钮情况与bsSingle一样。
5. bsSizeToolWin可以重设大小,只有关闭按钮和标题栏。
6. bsToolWindow不能重设大小,只有关闭按钮和标题栏。
注意:在设计时修改BorderIcon和BorderStyle属性并不立即反映出来。这些变化要到运行期才能看到效果。其实,TForm的大部分属性都是这样的,这是因为在设计时修改窗体的外观没有多大意义。例如,假设把Visible属性设为False,如果窗体不再显示出来,那么就无法操作窗体上的。
粘上标题!
你可能注意到了,上面的选项没有一个选项能够创建一个没有标题但可以重设大小的窗体。要实现这种特殊窗体,需要覆盖窗体的CreateParams()方法,然后设置相关的风格。下面的代码演示了这一点:

第21章"编写自定义组件"将进一步介绍CreateParams()的用法。
清单4-2列出了怎样在运行时修改BorderIcon和BorderStyle属性的有关代码:
清单4-2BorderStyle/BorderIcon项目的主窗体单元

注意:TForm的部分属性用于设置窗体的外观,部分属性用于设置窗体的行为。要想进一步了解TForm的属性,请查找Delphi5帮助。

4.4.1.4.窗体重用性:可视化窗体继承
Delphi5的一个重要功能就是可视化窗体继承。在Delphi的第一个版本中,可以创建一个窗体,然后把它保存为模板,以后可以在模板的基础上创建新的窗体。但这并不是真正的继承,因为无法访问祖先窗体的组件、方法和属性。而对于真正的继承来说,派生的窗体与祖先窗体可以共享相同的代码。
继承的优势在于,应用程序可以做得比较精巧。另外,当祖先窗体的代码改变时,派生窗体会跟着改变。
对象库
Delphi5提供了对象管理功能,允许程序员共享窗体、对话框、数据模块和项目模板。这个功能称为"对象库(ObjectKepository)"。通过对象库,开发者可以共享其他项目的对象。另外,通过继承对象库中已有的对象,可以最大程度地实现代码重用。第4章讲到了对象库。最好要熟悉对象库的功能。
提示:在网络环境中,可能要与其他程序员共享窗体模板,这时需要创建一个共享的库。在Environment|Options对话框中,可以指定共享库的位置。每一个程序员必须把一个网络驱动器映射到这个位置。以后当程序员使用File|New菜单命令时,Delphi就会检查共享库所在的目录。
继承另一个窗体是很简单的,因为这已成为Delphi5环境内置的功能。要基于一个已有的窗体创建一个新的窗体,只要使用File|New菜单命令,Delphi将打开New Items对话框。这个对话框列出了对象库中的所有对象。翻到Forms页,这里列出了所有已经加到对象库中的窗体。
注意:不必在对象库中查找就能继承一个本项目中的窗体。使用File|New菜单命令,然后选择Project页。在那里,可以选择一个本项目中已有的窗体。显示在Project页中的窗体并不在对象库中。
有一些列出的窗体就是以前加入到对象库中的。可能注意到,有三个选项用于把窗体加到项目中:Copy、Inherit和Use。
如果选择Copy,则意味着把所选窗体的副本加到当前项目中。如果对象库中的窗体发生变化,不会影响到当前项目中的副本。
如果选择Inherit,则意味着从所选窗体派生出一个新的窗体加到当前项目中。如果对象库中的窗体发生变化,则派生的窗体也会跟着变化。
如果选择Use,则意味着所选的窗体直接加到当前项目中,就好像这个窗体是当前项目创建的一样。在设计时对这个窗体的任何修改都会影响以Inherit方式使用这个窗体的项目。

4.4.2TApplication类
任何基于窗体的Delphi5程序都包含一个全局变量Application,它的类型是TApplication。TApplication封装了一些属性和方法,使应用程序能够正确地在Windows环境下运行。这些方法中,有的用于建立窗口类定义,有的用于创建应用程序的主窗口、激活应用程序、处理消息、添加上下文敏感的帮助以及处理VCL的异常。
注意:只有基于窗体的Delphi应用程序才有全局Application对象,而控制台程序和服务程序没有Application对象。
一般不需要关心TApplication在背后到底做了些什么,不过,有时需要了解TApplication的详细情况。
由于TApplication并不在Object Inspector中出现,所以不能在设计时修改它的属性,但可以用Project|Options菜单命令,翻到Application页,设置一些有关TApplication的属性。在大多数情况下,只能在运行时工作于TApplication的实例即Application。也就是说,只能在运行时设置Application的属性、方法和事件过程。

4.4.2.1.TApplication的属性
TApplication具有几个属性,可以在运行时访问它们。下面将介绍这些属性以及怎样通过它们改变Application的默认行为。这些属性在Delphi5的在线帮助中也有介绍。
(1)TApplication.ExeName属性
ExeName属性能够返回应用程序的全路径和文件名。这个属性在运行时是只读的,不能修改它。但是可以读它,以使用户知道应用程序是从哪儿运行的。例如,下面的代码把ExeName属性的值显示在主窗体的标题栏上:

提示:使用ExtractFileName()函数可以从ExeName属性中得到文件名:

使用ExtractFilePath()函数可以从ExeName属性中得到全路径:

使用ExtractFileExt()函数可以从ExeName属性中得到文件扩展名:

(2)TApplication.MainForm属性
在前面的例子中,可以看到怎样访问MainForm来修改它的Caption属性以显示应用程序的ExeName值。MainForm的类型是TForm,可以通过MainForm访问TForm的任何属性和方法。也可以访问加到派生窗体的属性,只要把MainForm强制转换为该窗体的类型:

MainForm是一个只读的属性。只能在设计时通过Project Options对话框上的Forms页把一个窗体指定为主窗体。
(3)TApplication.Handle属性
Handle属性是一个HWND(一个用于Win32 API的窗口句柄)。一般情况下,用不着访问Handle属性,除非要修改Application的默认行为,而Delphi又没有提供相应的方法。此外,调用某些Win32 API时可能也需要用到Handle属性,因为那些API需要传递应用程序的窗口句柄。后面将更详细地讨论Handle属性。
(4)TAppllcation.Icon和TApplication.Title属性
Icon属性用于设置当应用程序最小化时代表应用程序的图标。可以修改Icon属性来改变应用程序的图标。后面的4.6.1节"在项目中添加资源"将详细介绍这一点。
在Windows95/98的任务栏上,显示在图标右边的文字通过Title属性设置。如果应用程序在Wndows NT下运行,则文字显示在图标下面。下面的代码演示了怎样修改Title属性:

(5)其他属性
Active是一个只读的属性,它的值表明应用程序是否激活和具有输入焦点。
ComponentCount属性表明应用程序所包含的组件数,如果Application.ShowHint属性设为True的话,那么这些组件主要是指窗体和THintWindow实例(即提示窗口)。对于那些没有拥有者(owner)的组件来说,ComponentIndex属性总是-l。因此,TApplication.ComponentIndex总是-1。这个属性主要用于窗体和窗体中的组件。
Components属性是一个数组,它的元素就是那些属于Application的组件。Components数组的元素个数就是TApplication.ComponentCount属性的值。下面的代码演示了在一个列表框中列出所有组件的类名:

HelpFile属性用于指定帮助文件的文件名。需要向TApplication的HelpContext方法以及其他类似的方法传递帮助文件的文件名。
TApplication.Owner属性总是nil,因为TApplication不可能被另个组件拥有。
ShowHint属性用于设置是否允许显示提示条。Application.ShowHint属性覆盖其他组件的ShowHint值。如果Application.ShowHint属性设为False,则所有组件的提示条都不会显示。
如果应用程序的主窗体被关闭,或者调用了TApplication.Terminate(),则Terminated属性为True。

4.4.2.2.TApplication的方法
应当熟悉TApplication的有些方法。下面就讨论这些方法。
1.TApplication.CreateForm()方法
TApplication.CreateForm()是这样声明的:

这个方法用于创建一个窗体的实例,InstanceClass参数用于指定这个窗体的类,创建的实例由Reference参数返回。在项目文件中你可能看到过CreateForm的用法。例如,下面的代码创建TForml类型的实例Form1:

如果Form1出现在Project|Options对话框的Auto-Create列表中,Delphi5会自动生成上面这一行代码。不过,对于那些没有出现在Auto-Create列表中的Form,可以在程序的任何一个地方调用CreateForm()来创建它的实例。其实,CreateForm()相当于窗体本身的Create(),但TApplication.CreateForm()会检查MainForm属性是否为nil。如果是的话,CreateForm()会把新创建的窗体作为主窗体。一般情况下,不要调用CreateForm(),而要调用窗体本身的Create()。
2.TApplication.HandleException()方法
HandleExcePtion()用于显示项目中出现的异常的有关信息。信息将显示在一个由VCL定义的标准的异常信息框中。如果希望用自己的方式来显示异常信息,可以响应Application.OnException事件。后面的4.6.5节"覆盖应用程序的异常处理"将详细介绍。
3.TApplication的HelpCommand()、HelpContext()和HelpJump()方法
HelpCommand()、HelpContext()和HelpJump()分别提供了一种让应用程序与WINHELP.EXE程序提供的帮助系统交互的方式。其中,HelpCommand()用于执行一条WinHelp宏命令和帮助文件中定义的宏;HelpContext()用于打开一个帮助主题,主题的编号由Context参数传递;HelpJump()类似于HelpContext(),但它的JumpID参数需要传递一个字符串。
4.TApplication.ProcessMessages()方法
ProcessMessages()用于从Windows消息队列中检索任何等待处理的消息并进行处理。如果程序正在执行一个很长的循环而又不希望中断其他代码的执行(诸如响应"放弃"按钮),这时候就要用到ProcessMessages()。相反,TApplication.HandleMessages()如果发现没有消息,它就使应用程序处于空闲状态,而ProcessMessages()则不会使应用程序处于空闲状态。在第10章中将用到ProcessMessages()方法。
5.TApplication.Run()方法
Delphi5会自动把Run()放到项目文件的主代码块中。不要自己去调用这个方法,但需要知道这个方法到底干了些什么。首先,TApplication.Run()建立一个退出过程,以保证当应用程序退出运行时所有的组件都会得到释放。然后,它就建立一个循环来处理消息,直到应用程序终止。
6.TApplication.ShowException()方法
ShowException()需要传递一个异常类作为参数,它将显示有关该异常的信息的消息框。后面的4.6.5节"覆盖应用程序的异常处理"将详细介绍ShowException()。
7.其他方法
TApplication.Create()用于创建TApplication的实例。不过,这个方法是Delphi5内部调用的,应用程序本身不应调用它。
TApplication.Destroy()用于删除TApplication的实例。不过,这个方法是Delphi5内部调用的,应用程序本身不应调用它。
TApplication.MessageBox()用于打开一个Windows消息框。不过,这个方法不需要像Windows的MessageBox()函数那样传递窗口句柄。
TApplication.Minimize()用于把应用程序的主窗口最小化。
TApplication.Restore()用于把应用程序的主窗口恢复为最大化或最小化之前的大小。
TApplication.Terminate()用于终止应用程序的执行。与Halt()不同的是,Terminate()会隐含调用PostQuitMessage()看看还有什么消息要处理。
注意:调用TApplication.Terminate()可以终止应用程序。Terminate()会调用Windows的PostQuitMessage()函数向应用程序的消息队列中发一个消息。VCL据此释放应用程序创建的所有对象。要说明的是,并不是一调用Terminate()就马上使应用程序终止,而是当应用程序检索到WM_QUIT消息时才会真正终止。而Halt()立即终止应用程序的执行,但不释放先前创建的对象,也不会返回到调用Halt()的地方。

4.4.2.3.TApplication的事件
TApplication有一些事件,可以建立处理这些事件的处理方法。在旧版本Delphi中,TApplication的事件在Object Inspector上是找不到的(例如针对组件面板上的组件和窗体的事件)。要建立处理一个事件的处理方法,必须先声明一个作为处理方法的方法,然后在运行期动态地把此方法赋给某个事件属性。后面将以OnException事件为例说明怎样建立处理TApplication事件的处理方法。表4-2列出了TApplication的所有事件。表4-2 TApplication和TApplicationEvents的事件

本章的后续内容以及其他章节还将使用TApplication。
注意:TApplication.OnIdle事件适合于做一些不需要用户交互的动作,例如,在应用程序空闲的时候根据应用程序的状态更新菜单栏和工具栏。

4.4.3TScreen类
TScreen类封装了有关应用程序运行于的屏幕的状态信息。TScreen既不能作为组件加到窗体上,也不能在运行时动态地创建它。Delphi5会自动创建一个TScreen类型的全局变量叫Screen。TScreen的有些属性是很有用的,这些属性如表4-3所列。表4-3 TScreen的属性