用户名:  密码:   
您当前的位置:首页 > 程序设计 >  Delphi > 

Delphi 中用程序定义窗体的创建和显示顺序

上次修改时间: 2008-11-29 12:39:58      来源:网络总结      作者:钢铁长陈
上一页      路径:DSD\Learn\Delphi语言\Delphi相关\      下一页

在Delphi 中用程序实现自定义窗体的创建和显示顺序(1)

 

摘要:本文介绍了一种简单而实用的方法用来动态创建和显示窗体,在Project.dpr中编写代码实现窗体的动态选择创建,同时对 Delphi的窗体创建,应用程序的创建,运行和结束的机制作了深入的探讨。

关键字:Delphi 窗体 创建 显示 动态选择

 

一. 问题的提出

在Delphi 中我们可以通过两种方法创建多个窗体并设置他们的创建顺序,第一种方法是在新添加了多个窗体后,按Delphi 工具栏上的Project->Options… , 在出现的Forms 一页设置窗体的创建顺序。如下图:


第二中方法是在Delphi的工程文件中修改,在这里假设是Project1.dpr,其代码将显示如下:

 

program Project1;

 

uses

  Forms,

  Unit1 in 'Unit1.pas' {Form1},

  Unit2 in 'Unit2.pas' {Form2},

  Unit3 in 'Unit3.pas' {Form3};

 

{$R *.res}

 

begin

  Application.Initialize;

  Application.CreateForm(TForm1, Form1);

Application.CreateForm(TForm2, Form2);

Application.CreateForm(TForm3, Form3);

  Application.Run;

end.

         (代码1)

可以通过调整上面代码中语句的顺序来调整创建3个窗体的顺序,效果跟第一种方法一样。

但是上面这两种方法都存在一个缺陷,就是Delphi编译器默认会把排在第一位创建的窗体作为主窗体,每次程序启动都会首先显示主窗体,并且窗体的加载顺序是不变的。

如何做成可以根据需要动态改变程序加载顺序呢?

 

二.  Delphi窗体创建大揭密

1.  窗体权限的转移实验

新建3个Form , 在Project->Options… 中的Forms 一页把Form2和Form3 放置在 Available forms中,保留Form1在Auto-create forms中,让Form1在程序一开始运行就创建。

在Form1(主窗体)中的OnCreate()事件函数中加入以下代码:

procedure TForm1.FormCreate(Sender: TObject);

begin

  Label1.Caption:='Form1 Create Completed!';

  Form1.Show;

  Application.CreateForm(TForm2, Form2);

end;

    (代码2)

在Form2的OnCreate()事件函数中加入以下代码:

procedure TForm2.FormCreate(Sender: TObject);

begin

  Label1.Caption:='Form2 Create Completed!';

  Form2.Show;

  Application.CreateForm(TForm3,Form3);

end;

          (代码3)

在Form3的OnCreate()事件函数中加入以下代码:

procedure TForm2.FormCreate(Sender: TObject);

begin

  Label1.Caption:='Form3 Create Completed!';

  Form3.Show;

end;

       (代码4)

   这个程序在运行后将显示三个窗体,分别是Form1,Form2,和Form3。你可能在想,如果要关闭程序,只要关闭Form1这个主窗体就可以了。然而你错了,应该是关闭Form3才能将整个程序关闭。为什么呢?关键在CreateForm()这个窗体创建函数,查一下Delphi的随机帮助文件就清楚了。帮助文件有关CreateForm()函数的说明如下:

 

Call CreateForm to dynamically create a form at runtime. Developers do not need to add code for creating most forms, because typically one or more calls to CreateForm are added automatically to the project's main source when using the form designer.

 

CreateForm creates a new form of the type specified by the FormClass parameter and assigns it to the variable given by the Reference parameter. The owner of the new form is the Application object.

 

Note:      By default, the form created by the first call to CreateForm in a project becomes the application’s main form.

 

特别注意最后一句,默认地,在一个Project中第一个由CreateForm调用创建的窗体就成为应用程序的主窗体。这样就不难解释为什么需要关闭Form3才能关闭应用程序了。因为程序中第一个使用CreateForm的是在Project1.dpr中的一段代码:

begin

  Application.Initialize;

  Application.CreateForm(TForm1, Form1);

  Application.Run;

end.

这段代码是在Project.dpr中自动生成的,然后我们在Form1中的OnCreate()事件函数第二次使用CreateForm,这时Form1的创建还没结束( 因为OnCreate事件还没完成 ),程序的主窗体就已经改变了,变成了Form2 , 然后我们又在Form2中的OnCreate()事件函数第三次使用CreateForm,这时程序的主窗体变成了Form3 ,也就是说程序的生杀大权已经掌握在Form3手中。绝对不要再在Form3的OnCreate()事件函数中加入Application.CreateForm(TForm1,Form1),否则,程序将进入一个资源创建的死循环,一点一点地耗尽你的机器的内存。

从这里也可以看出Delphi并不是完全的面向对象,它不像Java那样可以随时随地创建一个窗体。

 

2.窗体权限的保留

   如果我们想在Form1中保留程序的权限,则需要新建一个Button,然后把Form1中的OnCreate()事件函数的代码放在Button的 OnClick()事件中。这时则只有关闭Form1,才能关闭整个程序。

 

3.动态选择创建窗体

回到我们一开始提出的问题,如何动态的有选择地创建窗体呢?我们用一个随机数模拟实际可能出现的动态情况,实际情况可能是程序在初始化时动态判断一个文件或数据库的一个值,由这个值决定是先显示Form2还是先显示Form3,还是只显示Form1 。代码如下:

 

procedure TForm1.FormCreate(Sender: TObject);

var

  randomnum:Integer;

begin

  Label1.Caption:='Form1 Create Complete!';

  Randomize;//设置随机器

  randomnum:=Random(10);//在0到10之间取随机值

  if (randomnum>0)and (randomnum<=4) then //如果随机值在0到4之间

  Begin

    Form1.Show;

    Application.CreateForm(TForm2, Form2);

  End

  else if (randomnum>4)and (randomnum<=8) then //如果随机数在4到8之间

  Begin

    Form1.Show;

    Application.CreateForm(TForm3, Form3);

  End

  Else     //如果随机数在8到10之间

  Form1.Show;

end;

    (代码5)
在Delphi 中用程序实现自定义窗体的创建和显示顺序(2)

 

 

 

 

以上方法虽然可以实现动态的有选择地创建和显示窗体,但是有一个致命的弱点,就是程序关闭权限的转移,不能保留Form1的程序关闭权限。怎样真正实现有选择的创建和显示窗体,并且能保持Form1为主窗体呢?方法如下:

把3个窗体都放置在Auto-create forms里面,即3个窗体都自动创建。

在Form1中设置一个全局变量

var

  flag:Integer;

 

然后把上面Form1那段代码改成如下所示:

procedure TForm1.FormCreate(Sender: TObject);

var

  randomnum:Integer;

begin

  flag:=0;

  Label1.Caption:='Form1 Create Complete!';

  Randomize;

  randomnum:=Random(10);

  if (randomnum>0)and (randomnum<=4) then

    flag:=2

  else if (randomnum>4)and (randomnum<=8) then

    flag:=3

  else

    flag:=1;

end;

   (代码6)

然后,在Form2的OnCreate() 事件中加入以下代码:

procedure TForm2.FormCreate(Sender: TObject);

begin

  Label1.Caption:='Form2 Create Completed!';

end;

在Form3的OnCreate() 事件中加入以下代码:

procedure TForm3.FormCreate(Sender: TObject);

begin

  Label1.Caption:='Form3 Create Complete!  (' +IntToStr(flag)+')  ';

  Form3.Hide;

  if flag=3 then

  Begin

    Form3.Visible:=True;

  End

  else if flag=2 then

  Begin

    Form2.Visible:=true;

  End

  else if flag=1 then

  Begin

    Form1.Show;

 end;

   (代码7)

还要记得把Form2和Form3的FormStyle属性改成fsStayOnTop,把Form1的FormStyle属性设成fsNormal,否则无法在屏幕的最前端显示所需要的窗体。把Form2和 Form3的Visible属性设成False,否则程序将无法正确地Focus(聚焦)于所需窗体。经过这样的修改之后就能根据随机结果选择最终要显示的窗体。

分析一下上面的过程可知道,窗体1,2,3是按顺序创建的,但是在Form1的创建过程中设置了一个全局变量作为记录的标记,记录应该显示的是哪个窗体,Form2的创建过程没有做任何动作,只是简单的一个显示信息,而在Form3的创建过程中则判断标志flag的值,由此决定究竟应该显示哪个窗体。

注意到主窗体是一定会显示出来的,这也是Form1的Visible没有设置成 False的原因,因为即使设置成False,也是会显示出来的。

以上所说的方法,其中的3个窗体都是已经被完全创建的,只是其中一个或两个没显示出来而已(设置成Visible为False),如果现在打开Spy++,可以清楚地看到3个窗口都在。但是我们的目的不仅仅如此,我们还要实现窗体创建的动态性和选择性。

前面说过使用Appliation.CreateForm()的方法来动态创建窗体有它的局限性,就是所谓的权限的转移。实际上我们可以改变这种情形,方法如下:

在Project1.dpr中只创建Form1

begin

  Application.Initialize;

  Application.CreateForm(TForm1, Form1);

  Application.Run;

end.

在Form1的OnCreate()中的代码和代码5一样。Form2,Form3的OnCreate()不需要任何代码, 这时程序的关闭权限还是没有回到Form1的手里,其实我们只要在Form1的OnClose()中加入一句就行了:

  Application.Terminate;

如果不加入这一句,则关闭Form1是不会关闭整个应用程序的,必须关闭Form2或Form3才行。 有些人可能会问,为什么不是Free而是Terminate呢?查一下Delphi的帮助文件关于Free的描述,其中有一个注意事项如下所述:

Warning: Never explicitly free a component within one of its own event handlers or the event handler of a component it owns or contains. For example, don’t free a button, or the form that owns the button, in its OnClick event handler.

可以看到,在Delphi中不能在一个组件它自己的事件处理中明确地释放这个组件。这就是为什么不能用Application.Free的原因。必须用Application.Terminate这一句来释放窗体创建的资源,否则将出现资源没有释放的错误。如何知道资源没有释放呢?在Delphi编译器则体现为Project在Running状态,并且需要通过Run->Program Reset来结束程序;另外,如果把程序Build了之后,脱离Delphi编译器直接运行程序,然后关闭Form1,这时候打开Windows的任务管理器,在进程一页将显示Project仍然在,占用着内存和CPU,需要通过结束进程来去掉它。

关于Application.Terminate,Delphi的帮助文件有这样的描述:

Call Terminate to end the application programmatically. By calling Terminate rather than freeing the application object, you allow the application to shut down in an orderly fashion.

这里,Delphi很明确的告诉我们,调用Terminate而不是Free,可以让应用程序以整齐的方式关闭。

到这里我们的任务还没结束,我们只是让Form1 和Form2或者是Form3有同等的关闭程序的权限,我们还要剥夺Form2或是Form3的程序关闭权,方法是在Form2和Form3的OnClose()事件函数中加入一句Application.Run,这样按Form2和Fomr3的关闭按钮都不会有什么反应了,当然也就不会关闭程序了。我们再添加一个按钮用来“关闭”窗体,在其Onclik事件中加入一句

Form2.Visible:=False;

所谓的关闭其实是把显示关闭,而不是把窗体关闭,窗体的资源是没有释放的。Delphi的帮助文件中关于Application.Run的描述不鼓励我们使用Run,但是从实际使用情况来看,并没有什么坏处,只是窗体的关闭按钮的功能被屏蔽掉了,但是我们有替代的办法,把窗体的BorderStyle属性设置成bsNone就可以把窗体的Title标题栏取掉,用其他按钮或者其他控件,代替标题栏上的最小化,最大化,关闭按钮。

到此为止,我们可以说是基本解决了本文开始提出的问题,但是还有一个重要问题我们忽略了,Fom1总是会出现,聪明的你可能想到了在Project.dpr中编写一些代码来处理,对极了,请看下面代码:

program Project1;

 

uses

  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,

  Dialogs, StdCtrls,

  Unit1 in 'Unit1.pas' {Form1},

  Unit2 in 'Unit2.pas' {Form2},

  Unit3 in 'Unit3.pas' {Form3};

 

{$R *.res}

var

  randomnum:Integer;

begin

  Application.Initialize;

  Randomize;//设置随机器

  randomnum:=Random(10);//在0到10之间取随机值

  if (randomnum>0)and (randomnum<=4)then//如果随机值在0到4之间

  Begin

    Application.CreateForm(TForm2, Form2);

  End

  else if (randomnum>4)and (randomnum<=8)then//如果随机数在4到8之间

  Begin

    Application.CreateForm(TForm3, Form3);

  End

  Else     //如果随机数在8到10之间

  Begin

    Application.CreateForm(TForm1, Form1);

  End;

  Application.Run;

end.

实现以上代码后,Form1,Form2,Form3不用写任何处理代码,不用修改任何属性,就能解决我们的问题,我们可能要慨叹了,为什么一开始没想到这样做。

 

三.  总结

使用Appliation.CreateForm()的方法来动态创建窗体有它的局限性,本文介绍了一个简单而实用的方法用来动态显示窗体,这种方法的关键是设置一个标志,根据实际情况给标志赋值,等到全部窗体创建完毕后,根据所设标志决定需要显示的窗体。但是这种方法的缺点是所有的窗体都将被创建。本文介绍了另外一种方法,这种方法能真正实现动态选择创建窗体,但是同样存在缺点,就是程序关闭权限被平均化了,但是我们有相应的解决办法。这两种方法各有其优缺点,为此我们用到第三种方法,在Project.dpr中编写代码实现窗体的动态选择创建,到此则完满地解决了本文开始提出的问题。