摘自:http://www.delphibbs.com/keylife/iblog_show.asp?xid=15901
第八章 Delphi的程序单元 Object Passal的程序结构很特殊,与其它语言如C++,Object Windows等结构都不同。一个Delphi程序由多个称为单元的源代码模块组成。使用单元可以把一个大型程序分成多个逻辑相关的模块,并用来创建在不同程序中使用的程序库。 8.1 Program单元 Program单元就是Delphi中的项目文件。 Program单元是一个特殊的单元,类似于C语言中的Main程序,即为应用程序的主程序。一个程序可以有多个单元组成,也可以只有一个Program单元组成,例如前面我们介绍过的DOS窗口程序就只有一个Program单元组成。下面是一个典型的Program单元:program Project1; uses Forms, Unit1 in 'Unit1.pas' {Form1}; {$R *.RES} begin Application.Initialize; Application.CreateForm(TForm1, Form1); Application.Run; end (1)程序首部指定程序名、以及参数等。 (2)Uses语句定义程序用到的所有单元。 标识符为单元的名字,各单元之间用逗好(,)隔开,最后结束用分号(;)。 注意:每个程序总是自动包含System单元,Program单元的Uses部分不能显式指定。System单元用于实现一些低级的运行时间程序的支持,如文件输入输出(I/O)、字符串操作、浮点运算、动态内存分配等。另外,Delphi在发行时提供了许多预先定义的单元,在构造程序时可以直接使用。例如,如果你将一个核对框放进一个窗体,你就自动地使用了StdCtrls单元,因为TCheckBox构件在StdCtrls中定义。 Uses部分列出单元的顺序决定它们初始化的顺序,并影响编译器定位标识符的顺序。如果两个单元定义了一个相同名字的类型,编译器将总是使用前面那个单元的类型。 (3)程序块由保留字Begin和End括起来的一段代码组成,用于对程序的初始化。 8.2 UNIT单元 UNIT单元相当于C语言的子程序。基本上Delphi每个窗体都一个对应的单元。当你为应用程序创建窗体时,你就创建了一个与该窗体相联系的新单元。然而,单元也可以独立于窗体而存在。例如,一个单元可以只包含数学运算程序,而不需要有窗体。 一个单元可以由多个程序共享。单元的磁盘文件名后缀为.pas。 8.2.1 单元结构 不管单元是否与窗体相关,单元的基本结构都是一样的 。UNIT单元由单元首部、接口部分(interface part)、实现部分(implementation part)、可选择的初始化部分(initialization part)、结束部分(finalization part)、end.组成。 8.2.2 单元首部 单元的首部用保留字Unit开始,后跟单元名。单元名必须遵循标识符的所有一般原则(不能以数字开头等)。下面的单元名将是有效的: Unit Rsgl; 8.2.3 接口部分(Interface) 在单元名之后是接口部分。接口部分用于声明变量、类型、过程和函数等。在接口部分声明的变量、类型以及过程、函数等是其它使用该单元的程序或单元等都可见的。接口部分用保留字Interface标明开始,用implemention标明结束。接口部分只能含有声明部分。 一个单元的接口部分还作为该单元说明文件的起点。虽然接口部分没有告诉你子程序做什么或变量如何使用,但它正确告诉了你的变量、类型、过程、函数等的名字及其调用方法。 接口部分本身又可以由几个可选的部分组成,分别是单元的USES语句、常量声明部分、类型声明部分、变量声明部分、过程和函数声明部分。其中常量声明、类型声明、变量声明、过程和函数声明部分用于声明其它使用该单元的单元可以访问的变量、类型、过程和函数等。 而USES语句列出该单元要用到的标准单元和其它单元,用于把外部的已定义的常量、类型、变量、过程或函数引入到本单元中使用。USES语句紧跟在Interface之后。 8.2.4 实现部分 单元的第二部分,称为实现部分(Implementation),主要用于定义接口部分声明过的过程、函数等的代码。实现部分用保留字implementation标明,总是紧随接口部分之后。 实现部分也可以用USES语句列出该单元要用到的标准单元和其它单元等。如上面的uses MDIEdit;语句。实际上,实现部分也可以声明变量、数据类型、过程及函数等。 但是,在实现部分定义的变量、类型、过程、函数等只能由本单元自己使用(private declarations),使用该单元的其它单元或程序不可见的。私有定义可以隐藏单元的细节。 8.2.5 USES子句 USES子句用于访问其它单元。例如,如果你要让程序来效仿一个电传打字机,可以在USES包含WinCRT,因为WinCrt含有进行这个仿效所需要的程序。 USES WinCRT; Delphi提供了许多预先定义的单元,你可以在程序中直接使用。实际上,当你将一个新构件放入设计的窗体时,DElphi会自动将该构件的单元放入USES子句中。例如,如果你将Color Grid放入窗体,则单元ColorGrd就附加在窗体单元的USES子句末尾,从而ColorGRd单元中接口部分所有定义都是窗体单元可以访问的。 要使用USES子句包含单元中的程序,只要在单元名后加上程序名即可。例如,如果要在Unit2中访问Unit1中的ComputePayMent函数。 USES子句可以放在接口部分(保留字Interface之后),也可放在实现部分(保留字Implementation之后),但是USES子句必须出现在它所有子程序、数据类型或变量被使用之前 。 USES子句放在实现部分可以隐藏单元的内部细节,同时可以避免循环引用发生。 8.2.6 初始化部分(Initialization) 初始化部分是单元的可选部分,主要用于对单元数据的初始化,一般很少用到。 该部分总是在其它程序代码之前运行。如果一个程序包含多个单元,则在程序的其它部分运行之前,每个单元的初始化代码都会先调用,其顺序是单元显示在Uses语句的顺序。 8.2.7 完成部分(Finalization) 完成部分(Finalization)是初始化过程的反过程,只要单元有初始化部分,才会有完成部分。完成部分对应在Delphi1.0中ExitProc和AddEXitProc函数。 完成部分在程序关闭时运行。任何在单元初始化时获得的资源如分配内存、打开文件等通常都在完成部分释放。单元完成部分的执行顺序与初始化部分相反。例如假如程序以A、B、C的顺序初始化单元,则完成过程的顺序是C、B、A。 一旦单元的初始化部分开始执行,就必须保证程序关闭时对应的完成部分执行。完成部分必须能够处理不完全初始的数据,因为如果产生异常,初始化代码可能不能完全执行。 第九章 处 理 异 常 解决错误是每个程序人员必有的经历,编写安全、健壮的应用程序是每个程序员的始终追求。然而,产生错误总是难免的,尽管程序员尽职尽责,即使程序没有一个错误,各种与程序一起工作的软件、硬件也可能发生错误。一个可靠的程序应该可以处理所有可能导致程序停止或产生不恰当结果的状态。 这种状态在Delphi中称之为异常(Exception)。Delphi提供了高级异常处理机制,可以确保程序从可能发生的错误中恢复过来,而且可以保证数据或资源不丢失,并在需要的情况下,关闭系统。 每个Delphi程序都有一个缺省的异常处理器,用来显示错误消息,防止程序意外停止。通过加入附加异常处理,用户可以更好地处理应用程序对意外条件的响应,以便当麻烦发生时,执行清理工作,保存重要数据和资源。下面,我们就来讨论有关Delphi异常处理机制的使用。 9.1 异常的产生 设计健壮程序的关键之一是,如果程序分配了资源,最后就必须释放它,而不管异常是否发生。例如,如果你的程序分配了一块内存,必须确保最终释放这块内存;如果打开了一个文件,必须确保最后关闭这个文件。 在正常情况下,程序在申请资源后,能够释放它。然而,就算异常发生了,也必须保证释放它。 异常往往由一些偶然的事件导致。例如,程序中调用一个RTL例程,或使用一个构件都可能产生异常。你的程序必须确保即使这些偶然事件发生了,也要释放被占用的资源。 常见的需要保护的资源有:文件、内存、Windows资源 、各种对象等。 9.2 异常处理语句 要处理异常,首先必须识别异常,判断异常在什么时候发生,然后对出现的异常作出处理。因此对于程序员来说,其工作就是确定程序中哪个地方产生错误及发生错误时的处理程序,特别是容易导致数据或系统资源丢失的错误。 被保护代码块是用户处理异常的基本工具。对异常的响应是在代码保护块中进行的。当有若干条语句需要处理同一个错误时,可以将这些语句放在一个代码块里,并对整个代码块定义一个响应过程。这个对异常响应的代码块称为保护块。 处理代码保护块异常及其异常处理有两种方式,即try...finally语句和try...except语句。这两种语句可以互相嵌套,形成更加牢固的异常处理结构。 9.2.1 try...finally 有时侯,我们只需要程序中操作某项任务的代码执行完成,而不管是否发生了异常。例如,当一段程序获得了某项资源时,我们需要释放它,而不管程序是否正常终止。在这种情况下,就可以使用try...finally语句。 try...finally语句的语法结构是: try statementList1 finally statementList2 end 这里statementList1和 statementList2是由分号隔开的一系列执行语句。程序首先从statementList1开始执行,如果一切正常,将依此执行statementList2中的语句。但是,当程序在statementList1执行时产生异常时,程序将直接从出错的地方跳到statementList2执行。因此,statementList2语句总是会被执行的,无论statementList1程序是否出错。statementList2语句通常用于释放程序占用的资源。 又例如,下面是一段操作文件的代码: Reset(F); try ... // process file F finally CloseFile(F); end; 文件用来存放在硬盘的资源,在打开后,不管操作情况如何,最后都必须关闭它,否则容易造成数据丢失。 但是,假如statementList2语句中出现了问题,并且触发了异常,则该异常就被传递到try...finally语句的外面,同时原来在statementList1语句中触发的所有异常都将丢失。因此,try...finally语句也有一定缺陷。 9.2.2 try... except 异常通常是在try... except中进行的。 try...except 语句的语法结构是: try statements except exceptionBlock end 这里statements是由分号隔开的一系列执行语句。exceptionBlock可以是由分号隔开的一系列执行语句,也可以是一系列专门处理异常的程序句柄。 处理异常的程序句柄的语法规则是: on identifier: type do statement 这里,identifier是任何合法的标识符,也可以省略,type表示异常的类型,statement是执行语句,但如果是复合语句,必须用begin...end括起来。处理异常程序句柄之后可以跟有else statements语句。 try...except 语句首先从statements开始执行,如果一切正常,exceptionBlock将被忽略,直接执行try...except 语句后面的语句。但是,当程序statements在执行中产生异常时,程序将直接从出错的地方跳到exceptionBlock执行。 exceptionBlock执行时,首先按顺序判断处理块中异常程序句柄中异常的类型,如果有一个与产生的异常类型相同,则执行类型后面的statement语句,如果没有与产生的异常类型相同的类型,但是在exceptionBlock后面有else子句,则程序转到该else子句执行。如果exceptionBlock仅仅是由一些普通的语句组成,则程序转到其中的第一条语句执行。如果在exceptionBlock什么也没有,则该异常将向外传递。 下面是一个例子: try ... except on EZeroDivide do HandleZeroDivide; on EOverflow do HandleOverflow; on EMathError do HandleMathError; end; 在上面的代码中,第一个异常处理句柄处理被0除异常;第二个异常处理句柄处理溢出异常;第三个异常处理句柄处理数学运算错误异常。这里,第三个异常处理句柄放在最后,是因为数学运算错误异常包含了前面两个异常类,如果它放在前面,则其它异常处理都不会被执行。前面我们看到,异常类名的前面可以加一个标识符。这个标识符用来在该异常句柄中表示该异常对象。这个标识符的使用范围只限于该异常句柄。例如: try ... except on E: Exception do ErrorDialog(E.Message, E.HelpContext); end; 如果异常块后面带有else子句,则else子句用于处理没有被exceptionBlock处理的任何异常。 except on EZeroDivide do HandleZeroDivide; on EOverflow do HandleOverflow; on EMathError do HandleMathError; else HandleAllOthers; end; 这里,else子句处理任何不是EMathError异常类的异常。如果exceptionBlock仅仅是由一些普通的语句组成,则产生的任何异常都会执行这些语句。例如: try ... except HandleException; end; 这里,HandleException例程将处理执行try和except之间语句时发生的任何异常。 9.2.3 嵌套异常响应 由于Pascal允许语句嵌套,因此,可以在已经定义了响应的保护块内再定义响应,即try...finally语句和try...except语句可以互相嵌套。 9.3 缺省的异常处理 如果你没有对异常作出响应,Delphi就提供缺省的处理方式。Delphi缺省异常处理会提示一个错误信息。Delphi缺省异常处理实际上是调用TApplication类的HandleException方法。 对于所有异常(EAbort除外), HandleException首先检查OnException事件句柄是否存在。如果存在,则执行这个事件句柄。如果不存在,就自动调用SHowException方法。 如果你既要处理异常,又要像VCL构件那样提供缺省的行为,可以在响应异常时,调用HandleException方法。例如下面代码 try { 保护语句 } except on ESomething do begin { 处理异常 } Application.HandleException(Self); {调用HandleException } end; end; 另外,OnException事件的处理代码可以被修改,可以用自己的代码代替缺省的异常处理句柄。办法就是响应OnException事件,参考下面的程序示例。这段程序显示,如果异常没有被处理,则调用缺省异常处理。缺省异常处理显示错误,结束程序。procedure TForm1.FormCreate(Sender: TObject); begin Application.OnException := AppException; end; procedure TForm1.AppException(Sender: TObject; E: Exception); begin Application.ShowException(E); Application.Terminate; end; 9.4 Exception类 Exception类是所有异常类的基类,其本身直接继承于TObject类,在Delphi的Sysutils单元中,Exception类的定义如下(只列出公共成员): Type Exception=Class(TObject) public Constructor Create(const Msg:string); Constructor CreateFmt(const Msg:string;const Args:array of const); Constructor CreateRes(Ident:Integer); Constructor CreateResFmt(Ident:Integer;const Args:array of const); Constructor CreateHelp(const Msg:string;HelpContext:Integer); Constructor CreateFmtHelp(const Msg:string;const Args:array of const;HelpContext:Integer); Constructor CreateResHelp(Ident,HelpContext:Integer); Constructor CreateResFmtHelp(Ident:Integer;const Args:array of const;HelpContext:Integer); Property HelpContext:Integer; Property Message:String; end; Exception类中只申明了两个特性:一个是Message特性,用于给出有关异常的简短说明;另一个是HelpContext特性,用于指定帮助的上下文编号,任何一个异常都可以访问这两个特性 。 Exception类提供了一些构造异常的方法。所有构造函数的名字都以Create开头,它们都有相同的目的,就是根据不同的参数来创建一个错误提示信息字符串。用户可以调用这些构造函数来创建自己的派生类的异常实例,或者可以为Exception对象调用它们。 例如,把一个字符串或变量作为错误消息来传递,生成异常的语句可以是: raise Exception.Create('Error In Memory'); 又例如,下面语句生成的异常显示带有两个整数的错误信息字符串: raise Exception.CreateFmt('Error:X=%d Y=%d',{X,Y]); 下面是这个语句的一个运行结果: Error:X=-1 Y=12 9.5 自定义异常 当用户需要产生异常时,可以选择从Exception中派生出来的一个类或自己创建异常类。用户可以从Exception中派生自己的类,也可以设计一个全新的类,这样的类不需要建立在Exception的基础上。但是,Delphi建议定义异常时,最好用Exception类作为基类,因为不是从Exception中派生的类必须处理所有不是基于Exception的异常,任何未处理的异常都会引起致命的应用程序错误。 自定义异常类的程序如下: type EMyException = class(Exception) 上面,定义了一个异常类,该类的基类是Exception. 当然,异常不一定必须直接从Exception类继承,也可以用其它Exception类的派生类作为自己的基类,例如: type EMathError = class(Exception); EInvalidOp = class(EMathError); EZeroDivide = class(EMathError); EOverflow = class(EMathError); EUnderflow = class(EMathError); 在声明异常时,可以根据需要声明一些字段、方法和特性。例如,SysUtils单元中的EInOutError异常就声明了一个ErrorCode字段,用于存储引起异常的文件I/O错误代码,示例如下: type EInOutError = class(Exception) ErrorCode: Integer; end; 声明了自己的异常后,需要在程序中触发这个异常,使用Raise语句。Raise语句的语法格式: raise object at address 这里object和 at address都是可选的。如果省略了object,表示要重新触发当前异常。 这种形式只能出现在try...Except结构的Except部分,主要用于过程或函数的调用中出现无法完全处理异常情况。raise后面的object是异常的对象实例,而不是异常的类型。通常把创建对象实例与触发这个异常并在一句中,因为构造函数返回的总是该类型的对象实例。如 EPasswordInvalid = class(Exception); 在触发异常时,创建对象实例与触发这个异常并在一句中。 if Password <> CorrectPassword then raise EPasswordInvalid.Create('Incorrect password entered'); 注意:虽然程序创建了异常对象实例,但程序不必自己删除它,因为异常处理句柄会自动删除异常的实例。 关键步骤是程序中定义了一个TMouseException类。其定义方法是: type TMouseException = class(Exception) X, Y: Integer; constructor Create(const Msg: string; XX, YY: Integer); end; TMouseException类是从Exception类派生出来的。除了从Exception继承来的成员外,TMouseException类还增加了整型成员X和Y,并说明了一个构件函数,可以调用这个构造函数来初始化该类的对象。其构造函数的编程为: constructor TMouseException.Create(const Msg: string; XX, YY: Integer); begin X := XX; // Save X and Y values in object Y := YY; Message := // Create message string Msg + ' (X=' + IntToStr(X) + ', Y=' + IntToStr(Y) + ')'; end; 这里,Message是从Exception来继承过来的,它被赋值给了包括这两个值的一个消息。 第十章 包 10.1 包 概 述 包是一种特殊的动态链接库。包可以用于Delphi应用程序,也可以用于IDE环境,或两者都可使用。包分为两大类:一类是运行期包,另一类是设计期包。运行期包是在程序运行时使用的,设计期包是在程序设计时使用的。一个包既可以用作运行期包也可用作设计期包。设计期包经常通过运行期包来发挥作用。 包包含完整的可视构件库以及其它对象、函数、过程等。与其它运行库相同,包包含的代码可以在程序之间共享。例如,经常使用的Delphi构件驻留在叫做VCL50的包中。每当你创建程序时,可以指定使用VCL50包,这样当对该程序进行编译时,执行程序只包含自己需要的代码和数据,而公共代码存放在VCL50.BPL中。如果一台计算机中有多个程序都使用包,则该计算机中只需要一份VCL50.BPL的拷贝。VCL50.BPL被所有程序共享,包括IDE本身。 通常,一个500KB的可执行程序可以减少到15KB左右。 选择View | Project Manager。 当Project Manager对话框出现并打开Project Options对话框时,用鼠标点出Project Options按钮;为打开对包的支持,在Project对话框中选择Packages标签,选择"Buildwith runtime Packages"复选框,再点击"OK"按钮。 10.2 运 行 期 包 运行期包随Delphi应用程序一起分发,并在程序运行时发挥作用。 要运行一个使用包的程序,计算机必须既装有应用程序的.EXE文件,还必须安装应用程序使用的包文件 (.BPL文件)。包文件必须存放在应用程序使用的系统路径下,并且版本符合应用程序的要求。 Delphi出厂时包含了一些预编译好的运行期包。这些包包含了大多数通用对象、构件,是编写窗体、按钮和控制、打印等应用程序经常要用到的。要想知道包含在每一个BPL中的对象和构件的更详细的说明,可参阅Delphi的联机帮助和指南。 下表列出一些预编译好的运行期包及其包含的单元。 包 包含单元 VCL50.BPL Ax, Buttons, Classes, Clipbrd, Comctrls, Commctrl, Commdlg, Comobj,Comstrs, Consts, Controls, Ddeml, Dialogs, Dlgs, Dsgnintf, Dsgnwnds,Editintf, Exptintf, Extctrls, Extdlgs, Fileintf, Forms, Graphics, Grids,Imm, IniFiles, Isapi, Isapi2,Istreams, Libhelp, Libintf, Lzexpand, Mapi, Mask, Math, Menu,Messages, Mmsystem, Nsapi, Ole2I, Oleconst, Olectnrs, Olectrls,Oledlg, Penwin, Printers, Proxies, Registry, Regstr, Richedit, Shellapi,Shlobj, Stdctrls, Stdvcl, Sysutils, Tlhelp32, Toolintf, Toolwin, Typinfo,Vclcom, Virtintf, Windows, Wininet, Winsock, Winspool, Winsvc VCLX50.BPL Checklst, Colorgrd, Ddeman, Filectrl, Mplayer, Outline, Tabnotbk, Tabs VCLDB50.BPL Bde, Bdeconst, Bdeprov, Db, Dbcgrids, Dbclient, Dbcommon, Dbconsts,Dbctrls, Dbgrids, Dbinpreq, Dblogdlg, Dbpwdlg, Dbtables, Dsintf,Provider, SMintf VCLDBX50.BPL Dblookup, Report DSS50.BPL Mxarrays, Mxbutton, Mxcommon, Mxconsts, Mxdb, Mxdcube,Mxdssqry, Mxgraph, Mxgrid, Mxpivsrc, Mxqedcom, Mxqparse,Mxqryedt, Mxstore, Mxtables, Mxqvb QRPT50.BPL Qr2const, Qrabout, Qralias, Qrctrls, Qrdatasu, Qrexpbld, Qrextra,Qrprev, Qrprgres, Qrprntr, Qrqred32, Quickrpt TEE50.BPL Arrowcha, Bubblech, Chart, Ganttch, Series, Teeconst, Teefunci,Teengine, Teeprocs, Teeshape TEEDB50.BPL Dbchart, Qrtee TEEUI50.BPL Areaedit, Arrowedi, Axisincr, Axmaxmin, Baredit, Brushdlg, Bubbledi,Custedit, Dbeditch, Editchar, Flineedi, Ganttedi, Ieditcha, Pendlg,Pieedit, Shapeedi, Teeabout, Teegally, Teelisb, Teeprevi, Teexport VCLSMP50.BPL Sampreg, Smpconst 你可以把定制的包加入到Delphi,这些包可以是你自己创建的,或者是向第三方供应商定购的。定制包的使用与运行期包一样,必须分发并安装在用户计算机上。要在程序中使用包,可以采取下列步骤: (1)在IDE装入或创建一个项目。 (2)选择Project|Options命令,打开.Project Options对话框,选择Packages标签。 (3)在Packages页下面的一个矩形框是运行期包区。其中"Build withRuntime Packages"复选框用于决定在执行程序编译时是否使用运行期时包。下面的编辑框用于指定要在项目中使用哪些包。与设计期包有关的运行期包已经列在编辑框中。 (4)选择"Build with Runtime Packages"复选框,并在编辑框手工输入一个或多个包名。输入的包名用分号隔开,并且只需要名称,不使用扩展名。例如,VCL50;VCLDB50;VCLDBX50。 (5)输入包名的另外一种方法是使用"Add"按钮。使用"Add"按钮打开"Add Runtime Package"对话框,在"package name "输入包名,或选择"browse"按钮寻找需要的包。当单击"OK"后,"package name "中的包名将添加到包名编辑框中。 包名编辑框中列出的包名将自动编译到当前项目中。如果编辑框中包名有重复,重复的包名将被忽略;如果编辑框是空的,则当前项目没有包。 通常情况下,在包名编辑框中列出的包只对当前项目有效。要使当前的选择成为任何新项目的缺省包,选择"Defaults"复选框。 10.3 设 计 期 包 设计期包,用于在Delphi的IDE 构件板上安装构件或用于在定制的构件中创建具有专用特性的编辑器。 Delphi出厂时在IDE预装了部分设计期包。下表列出了这些包及其包含的构件板页。 包 对应构件板页 DCLSTD50.BPL Standard, Additional, System, Win32, Dialogs DCLTEE50.BPL Additional (TChart component)DCLDB50.BPLData Access, Data Controls DCLMID50.BPL Data Access (MIDAS) DCL31W50.BPL Win 3.1 DCLNET50.BPL,NMFAST50.BPL Internet DCLSMP50.BPL Samples DCLOCX50.BPL ActiveX DCLQRT50.BPL QReport DCLDSS50.BPL Decision Cube IBSMP50.BPL Samples (IBEventAlerter component) DCLINT50.BPL (International Tools--Resource DLL wizard) RCEXPERT.BPL (Resource Expert) DBWEBXPRT.BPL (Web Wizard) 设计期包通常要调用运行期包,例如DCLSTD50就引用了VCL50。 除了预先安装的设计期包外,你可以把定制的构件包加入到Delphi的IDE,这些包可以是你自己创建的,或者是向第三方供应商定购的。新构件装入时,通常放在DCLUSR50设计期包内。要安装定制的构件,可以采取下列步骤: (1)如果要安装一个新包,将包文件拷贝到一个本地目录下。包文件包括.BPL、.DCP、 和.DCU文件等。其中存放.DCP和 .DCU文件的目录必须在Delphi库路径下。 (2)选择IDE中Component|Install Packages菜单命令,打开. Project Options对话框,(或者使用Project|Options命令打开图10.1所示的 ProjectOptions对话框)。 (3)对话框上面一个矩形框线围住的是Design Packages区。在"Designpackages"框中列出了所有可供选择的设计期包。用鼠标单击清单中的一项,在此清单下面的状态区中就会显示相应的路径和BPL文件名。包左边的复选框用于决定是否安装该设计期包。选择表示安装,不选择表示不安装。 这个框里也包含Add,Remove和Edit按钮,使你能够增加、清除和编辑在设计时使用的包。"components"按钮显示包含在被选中设计期包中的各个构件。 (4)单击"OK"。选择包中的构件将自动安装到IDE的构件板。通常情况下,选择的包只对当前项目有效。要使当前的选择成为任何新项目的缺省包,选择"Defaults"复选框。 如果要安装包,但不需要包中的某个构件,可选择Component|ConfigurePalette菜单命令,重新配置构件板,详细介绍请参考第二章中关于IDE的介绍 。 10.4 建立自己的包 Delphi为用户建立自己的包提供了向导,因此建立包的过程很简单。 10.4.1 准备工作 要创建一个包,事先要做好下列准备工作: (1)为包取一个名字。 (2)确定包需要的其它包的列表,即包的Require部分包含的包。 (3)包包含的单元列表,即包的Contain部分使用的单元。包实际上就是各种独立单元的包容器,包的功能通过这些单元体现出来。因此,在建立包之前,首先要确保这些单元没有错误,必要的话,使用内部集成的调试器调试这些单元。 10.4.2 建立包的一般步骤 (1)选择 File|New菜单命令,在"New Items对话框,选择"Package"图标,然后单击"OK"。打开"Package"编辑器。 (2)"Package"编辑器为新建包建立一个"Contains"节点和一个"Requires"节点。这里"Contains"节点包含新包需要的单元,"Requires"节点包含新包需要的其它包。 (3)要将一个单元添加到"Contains"中,选择"Contains"节点,然后单击"Add "按钮,打开"Add"对话框。在"Unit file name"编辑框输入单元文件名(后缀为.PAS),或者单击"Browse"按钮进行选择,然后单击"OK",则选择的单元文件加入到了包的"Contains"节点中。 (4)要将一个包添加到"Requires"中,选择"Requires"节点,然后单击"Add "按钮,打开"Add"对话框,见图10.6。在"Package name"编辑框输入包文件名(后缀为.DCP),或者单击"Browse"按钮进行选择,然后单击"OK",则选择的包文件加入到了包的"Requires"节点中。 (5)单击"Options"按钮,打开"Options "对话框。该对话框用于设置有关新包的属性。 ?.要创建只有在设计期才可用的包(运行期不能用),选择"Designtimeonly"单选按钮。 ?.要创建只有在运行期才可用的包(设计期不能用),选择"Runtime only"单选按钮。 ?.要创建在设计期和运行期都可用的包,选择"Designtime and runtimeonly"单选按钮。 (6)单击"Compile package"按钮,编译包。 编译时首先建立和保存包的源文件(.DPK)。包的源文件类似于Delphi的项目文件,它是由包编辑器自动维护的。建立了包的源文件后就编译此源文件生成相应的.DCP(类似于Delphi的.DCU文件)文件,最后生成包的可执行代码,扩展名为.BPL。 10.4.3 编辑包 包的源文件本身是一个文本文件,可以用代码编辑器进行修改和编辑。 (1)首先打开包。打开包方法有以下几种:?选择File|Open (或 File|Reopen)菜单命令,并选择DPK文件。?选择Component|Install Packages菜单命令,从设计期包列表中选择一个包,然后单击"Edit"按钮。当打开包编辑器后,在"Requires"选择一个包,然后右击,在弹出菜单中选择Open命令。 (2)打开包以后,就可以对包进行编辑了,其方法同创建一个包时一样。 (3)右击鼠标,在弹出菜单中选择"View Source"命令,则进入代码编辑器,从而可以像编辑窗体代码那样对包的源文件进行修改。例如,下面是VCLDB50包的源文件: package VCLDB50; requires VCL50; contains Db, Dbcgrids, Dbctrls, Dbgrids, Dbinpreq, Dblogdlg, Dbpwdlg, Dbtables, mycomponent in 'C:\components\mycomponent.pas'; end. 这里,"Contains"子句包含新包需要的单元,"Requires"子句包含需要的其它包。如果一个包不不需要其它包,可以没有"Requires"子句。 第十一章 窗 体 设 计 11.3 窗 体 特 性 我们曾经对窗体的特性、方法等进行过较为详细的讨论。这里,我们再对两个关于窗体类型及边界类型的窗体特性进行介绍。 设置窗体类型使用特性FormStyle。该特性允许用户有两种选择:一种是普通的SDI窗体――SDI意思是Single Document Interface(单文档接口),另一种是组成MDI应用程序的窗体――MDI的意思是Multiple Document Interface(多文档接口)。 下面列出了FormStyle特性的可能值: ?fsNorma1:窗体是普通的SDI窗口或对话框。 ?fsMDIChild:窗体是MDI子窗口。 ?fsMDIForm:窗体是MDI父窗口――也就是 MDI应用程序的框架窗口。 ?fsStayOnTop:窗体是SDI窗口, 但它总处于其它所有窗口的前面, 除了那些也被设置为"stay-on-top"(留在最前面)的窗口。 设置边框样式使用特性BorderStyle。该特性引用窗体的一个可视化元素TFormBorderStyle,有6个可能值: ?bsSizeable:窗体有标准的粗边框,可以拖动边框来调整窗体的大小。这是缺省的样式。 ?bsDialog:窗体有标准的对话框边框,它也是粗的,但不能改变大小。拥有这类边框的窗体也就是对话框。 ?bsSingle:窗体有一个细边框而且不能改变大小;它也叫作固定边框。与Windows 3.1不同,Windows 95在细边框与粗边框之间没有显示明显的区别。 ?bsToolWindow:窗体采用较细的标题栏(使用较小的字体),而且只有一个最小化Close按钮。这是一种不能改变大小的工具栏的特殊类型。 ?bsSizeToolWin:窗体采用较细的标题栏,与上一类型相似,但可以改变大小。 ?bsNone:窗体没有边框或任何传统的元素(标题、最大化与最小化按钮、系统菜单)。 11.4 固 定 窗 体 现在,我们来讨论窗体编程的技巧之一──固定窗口的位置。在Delphi的程序设计中,只需将窗体(Form)的特性BorderStyle取值为bsSizeable,用户就可随意变更窗体的大小和位置的移动。但在应用程序中使用Windows API函数同样可以改变窗体的这个特性值,并且还可以实现其它目的。 当我们在进行窗体移动和大小变更时,在此动作的前一时刻,Windows系统首先向窗体送出WM_WINDOWPOSCHANGING消息。由此消息产生的WM_WindowPosChanging事件句柄传递来的TWMWindowPosChanging消息记录型的WindowPos,是包含有变更动作内容的TWindowPos记录的指针。因此,我们可以根据对其内容的变更,来改变动作内容。 type TWindowPosMsg = record Msg:Cardinal; Unusrd:Integer; WindowPos:PwindowPos;//lparam 以下记录的指针 Result:Longint; end; 该TWindowPos记录包含以下内容: PWindowPos = ^TWindowPos; TWindowPos = Packed record; hwnd:HWND;//发生动作窗体的句柄 hwndInsertAfter:HWND;//变更Z顺序时最上层的窗体句柄 x:Integer;/窗体左端的位置 y:Integer;//窗体上端的位置 cx:Integer;//窗体的宽 cy:Integer;//窗体的高 flags:UINT;//变更的内容 在flags中,与此话题相关的标志如下: ?SWP_NOMOVE――保持现在位置(无视x成员和y成员) ?SWP_NOOWNERZORDER――不变更主窗体Z顺序的位置 ?SWP_NOSIZE――保持现在的大小(无视cx成员和cy成员) ?SWP_NOREDEAW――即使有变更,但不重写画面 ?SWP_NOZORDER――保持现在Z顺序(无视hwndInsertAfter成员) 据此当我们不想让窗体的大小变更时,只要在TWindowPos记录的flags变量中设置SWP_NOSIZE标志即可。同样不想让窗体移动时,则设置SWP_NOMOVE标志。 为了达到以上目的,首先要处理WM_WINDOWPOSCHANGING消息,修改flags变量的内容。作为窗体的处理过程(WMWindowPosChanging)如下: {禁止窗体移动、禁止大小变更处理} porcedure Tform1.WMWindowPosChanging(var Msg:TWMWindowPosChanging); begin inherited if bWinStat [1] then Msg.WindowPos^.flags := Msg.WindowPos^.flags or SWP_NOMOVE; if bWinStat [2] then Msg.WindowPos^.flags := Msg.WindowPos^.flags or SWP_NOSIZE; end; 此例中使用bWinStat变量(Boolean类型数组),来禁止窗体的移动和大小的修改。 11.5 固定窗体的横宽 这里我们继续详细介绍关于在窗体的大小修改中固定横宽的方法。 当我们修改窗体的大小或按下最大化按扭时,Window系统向应用程序窗体送出WM_GETMINMAXINFO消息。由于这个消息中包含有窗体尺寸的最大和最小值,如果我们适当利用此消息,则可以指定窗体的大小范围。首先定义WM_GETMINMAXINFO消息的句柄。 (1)WM_GETMINMAXINFO消息句柄的说明: type TForm1 = Class(Tform) procedure WMGetMinMaxInfo(var Msg:TWMGetMinMaxInfo); message WM_GETMINMAXINFO; (2)以TWMGetMinMaxInfo记录为参数: TWMGetMinMaxInfo = record Msg:Cardinal; Unused:Integer; MinMaxInfo:PMinMaxInfo; Result:Longint; end ; TWMGetMinMaxInfo中的MinMaxInfo是具有关于窗体大小信息的TMinMaxInfo记录的指针。 该记录在Delphi的Windows.Pas文件中是如下说明的: type {Struct pointed to by WM_GETMINMAXINFO Iparam} PMinMaxInfo = ^TMinMaxInfo; TMinMaxInfo = packed record ptReserved:Tpoint; //预留 ptMaxSize:Tpoint; //最大化时的大小 ptMaxPosition:Tpoint ; //最大化时的位置 ptMinTrackSize:Tpoint ; //尺寸变更时最小尺寸 ptMaxTrackSize:Tpoint ; //尺寸变更时最大尺寸 end; 当按下最大化按扭时,在ptMaxSize和ptMinSize中指定窗体的大小和位置。当拖动窗体框架时,ptMinTrackSize和ptMaxTrackSize确定其变化的最大和最小尺寸。 假如窗体的大小只在从 (100,100) 到 (500,400) 的范围内变化,那么,使用以下处理过程即可完成: //设置窗体大小变化范围 procedure TForm1.WMGetMinMaxInfo(var Msg:TWMGetMinMaxInfo); begin inherited; Msg.MinMaxInfo^.ptMaxTrackSize:= Point(500,400); Msg.MinMaxInfo^.ptMinTrackSize:= Point(100,100); end; 首先调用inherited方法,接着对MinMaxInfo构造体设置初始值,即设置ptMaxTrackSize和ptMinTrackSize的值。 这里并没有对按下最大化按钮时的大小进行设定,仅仅只是对拖动框架的大小的动作进行了限制。因此当按下最大化按钮时,窗体与平常一样占满全部画面。 当我们绝对不想让窗体的尺寸发生变更时,则采用以下处理过程: inherited; Msg.MinMaxInfo^.ptMaxSize:= Point(300,200); Msg.MinMaxInfo^.ptMaxTrackSize:= Point(300,200); 同样,横宽固定,纵长可自由改变的时候,则采用以下处理过程。 inherited; Msg.MinMaxInfo^.ptMaxSize.x= Width; Msg.MinMaxInfo^.ptMaxTrackSize.x = Width; Msg.MinMaxInfo^.ptMinTrackSize.x = Width; 上面代入的是该事件句柄所属的窗体的横宽,因此其窗体的横宽不能变更。 到此从功能上讲,我们已经能够对窗体的尺寸进行控制了。可是,现在你一定会注意到以下的问题:一个是全屏化按钮和图标化按钮的表现方法,另一个是对应尺寸变化的光标形状的变化。 关于全屏化按钮和图标化按钮的使用或不使用的控制方法,在Delphi的程序设计中,可通过直接设定窗体(Form)的BorderIcons特性值来控制。但是光标形状的变换控制稍微有点麻烦。当光标在窗体框架上时,光标显示为尺寸可变更,但是实际上窗体已经被固定。 为解决此问题生成适当的窗体,而后用WinSignt32(Delphi中含有)或Spy++等Windows监视系统对向该窗体传送的消息进行监视。当鼠标器在该窗体上移动时,Windows监视系统向窗体传送WM_MOUSEMOVE消息的同时,还传送WM_NCHITTEST消息。当移动和敲击鼠标时,系统对窗体传送WM_NCHITTEST消息,询问现在鼠标光标在窗体的什么位置,如果传回光标在窗体的框架上或窗体内,系统则相应自动改变光标的形状。 因此,我们先定义WM_NCHITTEST消息句柄,从该句柄传回值。就是说即使光标在框架上,传回值也不在框架上,给Windows系统一个小骗局。该消息句柄说明如下: interface type Tform1 = class(Tform) private bWinStat:array [1..4] of Boolean; procedure WMNCHitTest(var Msg:TWMNCHitTest); message WM_NCHITTEST; 该消息句柄的处理过程: //尺寸变更的光标形状不显示 porcedure Tform1.WMNCHitTest(var Msg:TWMNChitTest); begin inherited //当移动禁止时,则使其上端、左端尺寸不变更 if bWinStat [1] and (Msg.Result in [ HTTOP,HTLEFT,HTTOPLEFT,HTTOPRIGHT;HTBOTTOMLEFT]) then Msg.Result:= Windows.HTNOWHERE; //整体窗体尺寸变更禁止时 if bWinStat [2] and (Msg.Result in { HTLEFT,HTRIGHT,HTTOP, HTBOTTOM,HTTOPLEFT,HTTOPRIGHT,HTBOTTOMLEFT,HTBOTTOPMRIGHT]) then Msg.Result:= Windows.HTNOWHERE; //横宽方向尺寸变更禁止时 if bWinStat [3] and (Msg.ResultIn{ HTLEFT,HTRIGHT,HTTOPLEFT,HTBOTTOM,HTTOPLEFT,HTBOTTOPMRIGHT]) then Msg.Result:= Windows.HTNOWHERE; end; 单纯就窗体的框架而言,可分为上下左右4条边和4个角共8个模式。当移动禁止时,上端、左端的位置尺寸不得变更。 11.6 无标题栏窗口 现在,我们来讨论窗体编程的另一种技巧──建立无标题栏窗口。 为了使标题栏不显示,我们可以在窗体BorderStyle特性中选取bsNone值即可。但选取bsNone值后,窗体周围什么也没有。当我们只想去掉标题栏时又怎么办呢? 实际上窗体的位置、大小、显示风格等均由Windows API的CreateWindowEx函数所指定,这个API的参数是在Delphi的CreateParams过程中被初始化的。Delphi是依据以下的TCreateParams记录的值生成窗体的。TCreateParams记录包含窗体的位置、大小以及其他各种特性,在Delphi的Control.Pas单元中记录的定义为: TCreateParams = record Caption: PChar; //标题 Style: DWORD; //窗体的显示风格 ExStyle: DWORD; //窗体的扩展显示风格 X, Y: Integer; //显示位置 Width, Height: Integer; //窗体的大小 WndParent: HWnd; //父窗体 Param: Pointer; //指向窗口生成参数(WM_CREATE消息的 //LParam)的指针 WindowClass: TWndClass; //窗体的类别 WinClassName: array[0..63] of Char; //类别名 end; 该记录中包含窗体的位置、大小和窗体显示风格的标志。因此,一般通过其值的改变来控制生成窗体的位置、大小和窗体显示风格。由于我们要设置窗体初始的风格,所以在Params.Style变量中设定窗体的风格。又因为该变量是取标志形式,所以使用and或or布尔运算来演算,从而完成各个位的on或off。 例如,当BorderStyle特性为bsNone时,再给窗体加上可变更大小的粗框架,则对CreateParams方法进行重载。处理过程如下即可: Procedure TForm1.CreateParams(var Params:TCreateParams); begin inherited CreateParams(Params); if BorderStyle = bsNone then with Params do Style:= Style or WS_THICKFRAME; end; 需要注意,一般情况下的继承关系,单纯使用inherited,则派生类的方法即可被调出执行;但继承使用了CreateParams方法时,必须明确按inheritedCreateParams(Params)编制程序。 关于窗体风格标志值用以下定量来指定,这里列举的只是具有代表性的一部分。详情请参考Windows API的CreateWindow的help。 WS_BORDER边框 WS_CAPTION标题栏 WS_CHILD子窗体 WS_HSCROLL水平滚动条 WS_MAXIMIZE全屏化窗体 WS_MAXIMIZEBOX全屏按钮 WS_MINIMIIE图标化窗体 WS_MINIMIZEBOX图标化按钮 WS_SYSMENU系统菜单 WS_THICKFRAME可变更窗体大小的粗边框 WS_VISIBLE初始为可视的窗体 到此,没有标题栏的,大小可以变更的窗体已完成。 此窗体由于没有标题栏所以关闭、全屏化等按钮的操作均不可能,并且想通过拉拖标题栏移动窗体也不可能,下面我们先讨论关于移动的问题。 关于WM_NCHITTEST消息前面已经作了解说。当拖拉标题栏移动窗体时,窗体伴随鼠标器的移动,系统向窗体传送WM_NCHITTEST消息,当确认光标在标题栏上,并同时检测出鼠标器左按钮被按下时,移动成开始状态。 只要光标在窗体,则使窗体的移动成为可能。为此,WMNCHitTest处理过程如下。 porcedure TForm1.WMNCHitTest(var Msg:TWMNCHitTest); begin inherited; //当标题栏非显示时,光标在窗体领域即可移动 if (BorderStyle = brNone) and (Msg.Result = HTCLIENT) then Msg.Result:= HTCAPTION; end; 当鼠标光标在窗体领域内时,并且无标题栏,WM_NCHITTEST消息传回值为HTCLIENT时,将传回值作为HTCAPTION处理,系统则判断鼠标光标在标题栏上,这样就完成了窗体领域可移动的处理。这里再次给Windows系统一个骗局。 另外,讨论一下标题栏的消除和追加的方法。从而解决关闭、全屏化、图标化的问题。首先想到的方法是在窗体(Form)被单击或双击后,改变BorderStyle特性的值。就是说对Form的OnClick事件进行定义,创建子程序将bsNone变为bsSizeable。 可是我们测试一下,它并不动作。实际上这是因为在前面为了处理移动,在WMNCHitTest过程中,将窗体领域的返回值变更成了标题栏的返回值,因此系统只判断鼠标光标在标题栏上单击。 要想移动和单击同时起作用需要下一点功夫。当用WinSight32来确认显示正常的窗体返回给系统的消息时,与通常窗体领域返回WM_LBUTTONDOWN消息相对,在标题上单击时返回WM_NCLBUTTONDOWN消息。 这里不使用窗体(Form)的OnClik事件,而是通过WM_NCLBUTTONDOWN消息的处理来得到希望的动作。如下编程即可 : interface type TForm1 = Class(TForm); : protected WMNCLButtonDown(var Msg:TWMNCLButtonDown); message WM_NCLBUTTONDOWN; implemention //鼠标单击窗体追加标题栏 porcedure TForm1.WMNCLButtonDown(var Msg:TWMNCLButtonDown); begin if BorderStyle = bsNone then borderStyle = bsSizeble; end; 当然,如果在窗体上配置其他可视构件,从而可以简单地通过对构件的单击来完成以上动作。 11.7 窗体间相互连动 下面我们来讨论两个窗体的设计技巧。设其中一个为主窗体,另一个为子窗体。 在Delphi程序设计中,窗体(Form)的Position特性取值为poDesigned,则窗体的显示位置和大小以特性Left、Top、Width、Height的取值为依据来决定。这样在创建子窗体时,以获得主窗体的显示位置和大小(主窗体的Left、Top、Width、Height的特性值)为基准,然后确定子窗体的位置和大小即可。 窗体的初始显示位置,可用CreateParams方法进行设置,之后用重载子窗体(Form1)的CreateParams方法,来改变子窗体的显示位置和大小。主窗体和子窗体之间是怎样互相引用的呢?首先设置窗体(Form1)的单元文件名为unit1.pas,子窗体(Form2)的单元文件名为unit2.pas。通常要引用某窗体单元文件的特性和内容时,应该在interface的uses处插入其窗体(Form)的单元名(unit)。本实例中根据主窗体动态生成子窗体,因此在主窗体(Form1)的interface的uses处插入子窗体的单元名(unit2)。这样主窗体即可引用子窗体的各种特性值。另一方面要实现子窗体引用主窗体的各种特性值又怎么办呢?由于Pascal语言不允许相互循环引用,即A到B,并且B到A。这时,必须在imptementation的uses处插入主窗体的单位名(unit1),这样即可对主窗体(Form1)的特性值进行引用。 引用方法如下: implementation {$R *.DFM} uses Unit1;//主窗体单元名 {生成窗体时参考数的初始化} procedure TForm2.CreateParams(var Params: TCreateParams); begin inherited CreateParams(Params); //当没有窗体题目时,添加细框 if BorderStyle = bsNone then Params.Style := Params.Style or WS_THICKFRAME; //根据主窗体的显示位置和大小决定子窗体的显示位置和大小 with Form1 do begin Params.X := Left + Width; Params.Y := Top; Params.Height := Height; Params.Width := 100; end; end; 这样即完成了对应主窗体的子窗体在其右侧显示的程序设计。现在当我们移动主窗体时,子窗体并不随之移动,自然,移动子窗体时,主窗体也不会移动。虽然通常的Windows的应用程序则到此为止,但是我们的主题是要讨论连动,下面我们继续介绍这一问题。 首先要搞清楚,怎样把握自身窗体的移动和大小变化。在窗体的移动和大小变化时Windows系统要送出WINDOWSPOSCHANGED消息,为了捕捉此消息,对以下方法(Method)进行说明: type Tform1 = class(Tform) protected procedure WMWindowPosChanged(vax Msg:TWMWindowPosChanged); message WM_WINDOWPOSCHANGED; WM_WINDOWPOSCHANGED消息是在窗体的位置和大小变化后送出的。上面是对WM_WONDOWPOSCHANGED消息句柄的说明,接着是TWMWindowsPosChanged消息记录型和TWindowPos记录内容的定义。 type TWMWindowPosMsg = record Msg:Cardind; Unused:Integer; WindowPos:PwindowPos; Resukt:Longint; end; PwindowPos = ∧TWindowPos; TwindowPos = packed record hwnd:HWND; //自身的窗体句柄 hwndInsertAfeer:HWND;//变更Z的顺序时,最上层窗体句柄 x:Integer//新窗体左端的位置 y:Integer//新窗体上端的位置 cx:Integer//新窗体上端的宽 cy:Integer//新窗体上端的高 flags:UINT//变更内容(移动、大小 、Z顺序等) end; 下面具体介绍此方法的使用,首先要确认子窗体是否存在,而后确认自身的显示位置,一旦发生变更就使用WindowsAPI的SetWindowPos,以变更子窗体的显示位置和大小。要注意的是只是主窗体发生变更时才使用。 这样即可使子窗体随主窗体的变更而变更。接着我们还希望主窗体随子窗体的变更而变更。以下先是主窗体变更使子窗体变更的处理过程。 const uFlag = SWP_NOACTIVATE or SWP_NOZORDER or SWP_NOMOVE orSWP_NOSIZE; //大小变更、位置移动结束通知 porcedure TForm1.WMWindowPosChanged(var Msg:TWMWindowPosChanged); var Rect:TRect; x,y,cx,cy:Integer; uFlag2:UINT; begin inherited; if Form2 <> nil then //如果子窗体存在 begin GetWindowRoct(Form2.Handle,Rect); //获取子窗体的位置 With Msg do begin x:= WindowPos^.x + WindowPos^.cx; //新x坐标 y:= Window^.y; //新y坐标 cx:= 100; //宽固定在100 cy:= WindowPos^.cy //新高度 end; uFlag2:= uFlag; //需要移动时 if(Rect.Top <> Y) or(Rect.Left<>X) then uFlag2:= uFlag2 and(not SWP_NOMOVE); if(Rect.Bottom -Rect.Top <>cy) or //需要变更大小时 (Rect.Right -Rect.Left<>cx) then uFlag2:= uFlag2 and(not SWP_NOSIZE); if uFlag <> uFlag2 then //对子窗体(Form)进行变更 setWindowPos(Form2.Handle,0,x,y,cx,cy,uFlag2); 以上处理是从主窗体的新位置来计算子窗体的新位置,然后与现在的子窗体的位置信息进行比较,如果不同则变更子窗体。单纯用Wondow API的SetWindowPos对位置进行变更时,Z顺序和活动窗口(Active Window)发生转换,因此指定SWP_NOACTIVATE标志和SWP_NOZORDER标志,阻止其转换。 从子窗体来变更主窗体的过程大致与上相同。不同之处在于主窗体一定存在,而不需要对其进行检验。
2010年11月8日星期一
Delphi的程序单元结构和处理异常等相关内容 (转)
订阅:
博文评论 (Atom)
没有评论:
发表评论