delphi中正常窗口的实现
摘要 在delphi的vcl库中,为了使用以及实现的方便,应用对象application创建了一个用来处理消息响应的隐藏窗口。而正是这个窗口,使得用vcl开发出来的程序存在着与其他窗口不能正常排列平铺等显得有些畸形的问题。本文通过对vcl的深入分析,给出了一个只需要对应用程序项目文件作3行代码的修改就能解决问题的方案,且不需要原有的编程方式作任何改变。
关键字 vcl,正常窗口,正常化
1 引言
用delphi所提供的vcl类库编写的windows应用程序,有一个明显不同于标准windows窗口的特点--主窗口的系统菜单与任务栏上的系统菜单不相同。一般情况下,主窗口的系统菜单有六个菜单项而任务栏系统菜单只有三个菜单项。实际使用中我们发现用vcl开发的程序有以下几个方面的尴尬:
1)不够美观。这是肯定的,与标准不符自然会显得有些畸形。
2)主窗口最小化时没有动画效果。
3)窗口不能正常与其它窗口排列平铺。
4)任务栏系统菜单具有最高的优先级。在存在模态窗口的情况下整个程序仍然可以被最小化,与模态窗口的设计相违背。
主窗口最小化动画效果的问题在delphi 5.0以后的版本中已通过forms.pas中的showwinnoanimate函数解决,但其余几个问题则一直存在。尽管多数情况下这不会对应用程序带来什么影响,但在一些追求专业效果的场合确实不可接受的。由于c++ builder与delphi使用的是同一套类库,所以上述问题同样存在于使用c++ builder编写的windows应用程序中。
在以前的文章里(阿甘的家中可以找到),我已讨论过这个问题,当时的叙述看起来基本上是一种取巧的方法,而我也是在偶然之中才找到那个方法的。本文的任务就是通过对vcl类库作一些分析,说明那样做的原理,其次再给出一个只用3行代码的方法,完完全全地解决delphi中这个"非正常窗口"的问题。
2 原理
2.1 应用程序的创建过程
下面是一个典型的应用程序的delphi工程文件,我们注意到一开始就有一个对application对象的initialize方法的引用,我们的分析也就从这里开始:
program project1;
uses
forms,
unit1 in 'unit1.pas' {form1};
{$r *.res}
begin
application.initialize;
application.createform(tform1, form1);
application.run;
end.
隐藏的窗口是由application对象创建的,那么application对象又从何而来呢?在delphi的代码编辑窗口中按住ctrl点击application就会发现,application对象是在forms.pas单元中定义的几个全局对象之一。这还不够,我们想要知道的是application对象是在什么地方创建的,因为必须成功创建了tapplication类的实例我们才能引用它。
想一下,有什么代码会在application.initialize之前执行呢?对了,是initialization代码段中的代码。认真调试过vcl源码就可以知道,vcl中很多单元都有initialization代码段,启动delphi程序时,先是按照uses的顺序执行每个单元中initialization代码段的代码,完成所有的初始化动作之后才执行application的initialize方法以初始化application,所以很显然,application对象是在某个单元的initialization代码段中创建的。
以"tapplication.create"为关键字在vcl源码目录中搜索一番,我们果然在controls.pas单元中找到了创建application对象的代码。在controls.pas单元的initialization代码段,有一句对initcontrols过程的调用,而initcontrols的实现则如下所示:
unit controls;
…
initialization
...
initcontrols;
procedure initcontrols;
begin
...
mouse := tmouse.create;
screen := tscreen.create(nil);
application := tapplication.create(nil);
...
end;
好,到这里我们的分析就完成了第一步,因为要解决非正常窗口的问题,我们必须要在application对象初始化之前做一件事,因此了解应用程序的初始化过程就非常重要了。
2.2 islibrary变量
islibrary变量是在system.pas单元中定义的全局标志变量之一。如果islibrary的值为true则表明程序模块是一个动态链接库,反之就是一个可执行程序。vcl类库中的某些过程就根据这个标志变量的不同值完成不同的动作。也就是这个变量,在解决delphi的非正常窗口问题中起到了关键性的作用。
前面说过,为了方便,application对象初始化时创建了一个看不见的窗口(也就是用spy++之类的工具看到的那个以"tapplication"为类名的窗口),但也正是因为这个看不见的窗口,才使得用delphi开发出来的程序呈现诸多畸形。好了,如果我们能够去掉这个看不见的窗口(同时去掉任务栏系统菜单),代之以我们的应用程序主窗口,岂不是所有的问题都解决了?
说说简单,但实现起来需要对vcl源代码动大手术吗?如果那样岂不是有点本末倒置了?答案当然是不会,否则也不会有这篇文章了。在此我想说的是,在接下来的分析中,我们将会看到,所谓"编程之道,存乎一心",tapplication设计中无心插柳的做法,实则为我们解决这一问题留下了接口。不做源代码的分析,你可能要绕打圈子,而实际上我们会看到,天才的设计留给我们用的东西,不多也不少,刚刚好。
打开tapplication类的构造函数create,我们会发现这样一行代码。
constructor tapplication.create(aowner: tcomponent);
begin
...
if not islibrary then createhandle;
...
end;
这里说的是,如果程序模块不是动态链接库,那么就执行createhandle,而createhandle所做的工作在帮助中是这样说的:"如果不存在应用程序窗口,那就创建一个",这里的"应用程序窗口"就是上面所说的看不见的窗口,也即是罪魁祸首之所在,在tapplication类中用fhandle变量来保存其窗口句柄。这里就是根据islibrary的值完成了不同的动作,因为在动态链接库中一般并不需要消息循环的,但用vcl开发动态链接库还是要用到application对象,所以有了这里的设计。好,我们只需要欺骗一下application对象,在它创建之前把islibrary赋值为true,即可滤掉createhandle的执行,去掉这个讨厌的窗口了。