1.1 关于Delphi的BDE
Delphi操作数据库主要是利用BDE来进行。BDE是基于32位Windows内核的数据库引擎和连接工具,支持现有的大多数数据库。它具有如下一些特点:
1. 为多种数据库格式提供统一的应用程序接口,包括任何ODBC数据源。
2. 适应C/S数据库应用的开发,程序设计人员可以访问所有本地和服务端的数据,并且很容易实现应用的向上兼容性。
3. 对于Delphi标准的数据库访问(如Paradox和dBASE),BDE提供的连接方式是最快的。
4. 直接实时的访问数据源。
1.1.1 如何自定义BDE的驱动程序
BDE中按照各个文件的作用可分为以下几部分,这些文件均保存在“…/Program Files/Common Files/Borland Shared/BDE”目录下。
1. 必备文件
Blw32.dll:语言驱动函数库。
Idapi32.dll:BDE基本函数库。
Fareast.bll、Usa.bll:远东语言及美国语言驱动程序,对中文软件不可缺少。
Idr20009.dll:错误信息库。
2. 数据库驱动程序(可选)
Idasci32.dll:Ascii文本数据库驱动程序函数库;
Iddao32.dll:Access数据库驱动程序函数库;
Iddbas32.dll:dBase数据库驱动程序函数库;
Idodbc32.dll:ODBC数据库驱动程序函数库;
Idpdx32.dll:Paradox数据库驱动程序函数库。
3. 其他驱动程序和配置文件(可选)
Idbat32.dll:批操作驱动程序函数库,如果不用TBatchMove组件或DbiBatchMove类函数,可以不要。
Iddr32.dll:Data Repository驱动程序函数库,如果不用Data Repository功能,可以不要。
Idprov32.dll:BDE DataSet provide驱动程序函数库,如果不用TProvider组件,可以不要。
Idqbe32.dll:QBE驱动程序函数库,如果不用Query By Example,可以不要。
Idsql32.dll:SQL查询驱动程序函数库,如果不用TQuery进行查询,可以不要。
Idapi32.cfg:BDE配置文件,如果程序中没有特殊要求,可以不要。也可以在BDE管理器中设置正确后再分发。
4. 其他文件(一般不用)
BDE32.HLP、BDE32.CNT:BDE帮助文件。
BdeAdmin.exe、BdeAdmin.HLP、BdeAdmin.CNT:BDE管理器及帮助文件。
*.BLL:其他国家和地区的语言驱动程序。
DataBump.EXE、DataBump.HLP:数据库数据转移工具及帮助文件。
Localsql.HLP、Localsql.CNT:SQL查询语句帮助文件。
Sqllnk32.HLP、Sqllnk32.CNT:SQL连接帮助文件。
1.1.2 如何注册BDE
安装BDE仅仅复制前面提到的各种文件是不行的,还要等到向注册表注册之后才可以使用。
必需的注册表项目包括:
1. BDE动态链接库文件位置设置(如图1所示)
Key:HKEY_LOCAL_MACHINE/Software/Borland/Database Engine
Item:DLLPATH
Value:BDE动态链接库文件所在位置
图1 BDE动态链接库文件位置设置
2. BDE语言驱动文件路径设置(如图2所示)
Key:HKEY_LOCAL_MACHINE/Software/Borland/BLW32
Item:BLAPIPATH
Value:BDE语言驱动文件所在路径
图2 BDE语言驱动文件路径设置
3. 定义可用的BDE语言驱动文件(如图3所示)
Key:HKEY_LOCAL_MACHINE/Software/Borland/BLW32
Item:LOCALE_LIB#" (#表示数字, 如"LOCALE_LIB1"、"LOCALE_LIB3"等)
Value:指定各BDE语言驱动文件
图3定义可用的BDE语言驱动文件
1.1.3 如何在运行期检测和建立数据库别名
在前面学习的案例中,数据库别名的建立、修改和删除等维护工作,一般在BDE Administrator中进行,并在设计期就已经设置完毕,但这些功能在运行期将如何实现呢?在Delphi的BDE中,当数据库类型为STANDARD时,其别名定义最为简单,但这时仅能使用Paradox,dBASE,ASCIIDRV三种数据库作为默认的驱动程序。另外还需定义数据库存储路径(PATH)和ENABLE BCD,这样才能建立一个完整的数据库别名。
Delphi的数据库应用程序能自动提供一个Session组件,这个Session组件即为应用程序与BDE的接口。
1. 检测别名
通过调用Session.GetAliasNames(list:Tstrings)方法,可将当前BDE配置中的所有数据库别名的名称存放到List字符串列表中。然后调用list.IndexOf(需要检测的别名')函数,该函数的返回值可用来检测数据库别名是否存在(若其值为-1则不存在)。
2. 添加别名
使用Session组件的过程AddStandardAlias(constName,Path,DefaultDriver:string),可以添加一个标准类型的数据库别名。如添加一个数据库别名为NewAlias,其默认数据库驱动程序为Paradox、存储路径为c:/instance1,则可以使用下面的语句。
Session.AddStandardAlias('NewAlias',' c:/instance1','Paradox');
3. BDE配置文件存盘
使用Session.SaveConfigFile语句可将设置信息保存。
一般可以在窗体的OnCreate事件中添加检测和创建数据库别名的程序代码,下面就是一个在运行期实现上述功能的例子。首先单击Delphi工具栏上的“New Form”按钮,新建一窗体,在该窗体上添加一个Table组件,用以提供TSession对象。然后在窗体的OnCreate事件中添加如下所示的代码,程序执行时,系统会首先检测NewAlias别名是否存在,若不存在,提示用户是否创建这个别名,如图4所示。
Procedure TForm1.FormCreate(Sender: TObject);
var
sl:TStringList; //字符串列表变量
re:Integer;
begin
sl:=TStringlist.Create;
Session.GetAliasNames(sl); //取得别名列表
if (sl.IndexOf('NewAlias')=-1) then //判断别名是否存在
begin
re:=Application.MessageBox('别名NewAlias不存在,现在创建吗?','BDE信息窗口',mb_OKCancel);
if re=IDCANCEL then begin
sl.Free;
Exit;
end;
Session.AddStandardAlias('newAlias','c:/instance1','Paradox');
//增加一个名为NewAlias的数据库别名
Session.SaveConfigFile; //保存BDE配置文件
end ;
sl.free;
end;
图4 提示信息
1.1.4 Delphi连接数据库的方式有哪些
---- Delphi访问数据库的方式主要有3种,包括直接访问,如访问标准数据库类型Paradox和dBASE;通过ODBC访问,如访问Access、Foxpro等数据库;通过内嵌(Native)方式访问数据库,如访问SQLServer、Oracle、DB2等。下面以Table组件为例说明这3种方式的特点和用法。
1. 直接访问
----Delphi可以直接访问诸如Paradox和dBASE的标准数据库类型,而不需要什么特别的设置,只需要把文件路径或数据库别名赋值给Table组件的属性DatabaseName就可以访问该路径下的数据表了。
2. 通过ODBC访问
----访问Paradox和dBASED以外的数据库,通常可通过ODBC来实现。Delphi可以访问支持ODBC的数据库系统,如Access、SQLServer和Oracle等。当然,通过ODBC访问数据库时,首先要使用Windows的控制面板或Delphi的数据库引擎建立ODBC数据源。
3. 通过内嵌方式访问
Delphi中还可以不使用ODBC,而以内嵌方式访问SQLServer、Oracle、DB2等数据库系统。这需要使用数据库别名来指定数据库,数据库别名可以事先建立,也可以在程序运行时动态创建。前者称为静态别名,后者称为动态别名。使用数据库别名来访问数据库的方法和使用ODBC数据源的情形相同,这里不再赘述。
通过内嵌方式访问数据库的静态别名必须在BDE中建立。以访问SQLServer数据库为例,在建立别名时必须指定数据库服务器的名称(Servername)、主机名(Hostname)以及要访问的数据库名称(Databasename),可以指定登录用户名(Username)和密码(Password)等。
图5 设置Database组件
通过动态创建的别名来访问数据库必须使用Database组件。用户可以用鼠标双击Database组件,出现参数设置窗口,如图5所示。在“Drivername”一栏选择要访问的数据库系统,如“MSSQL”,然后选择按钮“Defaults”,就会把BDE中该数据库系统所需的参数名称和缺省值加入到“Parameteroverrides”列表中。根据实际情况更改参数中的“Servername”、“Databasename”等项。最后单击OK按钮完成设置。
比较可知,通过内嵌方式访问数据库要比通过ODBC访问数据库速度快一些。而且,内嵌方式可以在程序中动态设置连接数据库所需的参数,用户可不必设置ODBC数据源,从而降低了对用户技术水平的要求,并且减少了用户的工作量。因此从系统配置的难易和复杂程度来看,使用内嵌方式开发出的数据库应用系统更便于普通用户使用。
1.1.5 如何获取BDE的版本信息
单击Delphi工具栏中的“New Form”按钮,新建一窗体。向该窗体中添加1个Memo组件,并设置其Align属性为alclient。在窗体对应的单元文件的USES部分添加dbierrs, DBTables两个引用库文件。在下面的单元文件中,fDbiGetSysVersion函数将返回一个称为SysVersion的结构,并将最后结果显示在窗体的Memo中。
/*该窗体的单元文件代码*/
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs,dbierrs, DBTables, StdCtrls;
type
TForm1 = class(TForm)
Memo1: TMemo;
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
function fDbiGetSysVersion(SysVerList: TStringList): SYSVersion;
var
Month, Day, iHour, iMin, iSec: Word;
Year: SmallInt;
begin
Check(DbiGetSysVersion(Result));
if (SysVerList <> nil) then
begin
with SysVerList do
begin
Clear;
Add(Format('数据引擎版本号=%d', [Result.iVersion]));
Add(Format('接口级=%d', [Result.iIntfLevel]));
Check(DbiDateDecode(Result.dateVer, Month, Day, Year));
Add(Format('版本日期=%s', [DateToStr(EncodeDate(Year, Month, Day))]));
Check(DbiTimeDecode(Result.timeVer, iHour, iMin, iSec));
Add(Format('版本时间=%s', [TimeToStr(EncodeTime(iHour, iMin, iSec div 1000, iSec div 100))]));
end;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
var hStrList: TStringList;
Ver: SYSVersion;
begin
hStrList:= TStringList.Create;
try Ver := fDbiGetSysVersion(hStrList);
except
ShowMessage('BDE not installed !');
end;
Memo1.Lines.Assign(hStrList);
hStrList.Destroy;
end;
end.
按F9键运行程序,将得到如图6所示的BDE版本信息。
图6 运行结果窗口
1.1.6 如何维护DBF数据库
事实上,由于Delphi具有快捷易用、功能强大等优点,使得很多FoxPro和VFP的程序设计人员加入到了Delphi阵营。但在FoxPro和VFP的开发环境下,有许多DBF类型的数据表文件需要移植到Delphi中来,因此在Delphi中如何维护和操作这些数据将是一个很重要的问题。下面阐述在Delphi中完成DBF数据库的如下操作:真正删除记录、显示被删除记录、获取当前记录号和恢复被删除记录.
1. 真正删除记录
在Delphi程序中,使用Table或Query组件的Delete方法执行删除记录操作时,系统执行的是软删除,即相当于Foxpro中的“Set Delete Off”的效果。该方法只是将记录用星号标记为删除,实际并没有进行物理上的删除。要想真正删除记录,需要调用BDE函数DbiPackTable,该函数语法为:
functionDbiPackTable(hDb:hDBIDb;hCursor:hDBICur;pszTableName:PChar;pszDriverType:PChar;bRegenIdxs:Bool):DBIResult;
其中hDb为数据库Databse的句柄,hCursor为数据表Table的句柄,pszTableName为要删除记录的数据表名,pszDriverType为要删除记录的数据表的类型,bRegenIdxs表示是否在删除记录后自动更新索引文件。在四个参数中,hDb不能为NULL。hCursor、pszTableName、pszDriverType可以为NULL,但必须提供足够的信息来标识数据表的文件名和类型,当hCursor不为空时,pszTableName和pszDriverType可以为NULL;当pszTableName为数据表的路径和文件名时,hCursor和pszDriverType可以为NULL。
值得注意的是,在删除记录时,如果用Table来实现,则Table必须以Exclusive=True的方式打开。
2. 显示或不显示软删除记录
当DBF数据库中的记录执行软删除后,默认情况下在DBGrid等数据库显示组件中是看不见这些记录的。可以用BDE函数来控制是否显示DBF数据库中被软删除的记录,如同在Foxpro中利用语句Set Delete ON/OFF开关一样。执行这一功能的函数为DbiSetProp,其语法为:
functionDbiSetProp(hObj:hDBIObj;iProp:Longint;iPropValue:Longint):DBIResult;
该函数用来设置DBI对象中某个属性的值。其中Obj为DBI对象名称,这里为数据表Table的句柄,iProp为属性名,可使用软删除属性curSOFTDELETEON,iPropValue为属性值。
3. 获取当前记录号
在Delphi中可以使用BDE函数获取当前记录在数据集中的记录号.该函数为DbiGetRecord,其语法为:
functionDbiGetRecord(hCursor:hDBICur;eLock:DBILockType;pRecBuff:Pointer;precProps:pRECProps):DBIResult;
该函数用来取得当前记录的一些属性。其中,hCursor为数据集的Handle,eLock为对记录加锁的类型,pRecBuff存放记录的缓冲区,precProps为记录属性集,这里面就包含当前记录的记录号。
4. 恢复软删除记录
在Delphi应用程序中,对DBF数据表执行的删除操作为软删除操作。由于物理记录并没有从数据表中删除,因此可以恢复被软删除的记录,只要去掉删除标志即可。完成这一功能需使用函数DbiUndeleteRecord,其语法为:
functionDbiUndeleteRecord(hCursor:hDBICur):DBIResult;
其中,hCursor可以是数据集的Handle。
1.1.7 如何备份数据表
对于数据库应用来说,数据表的备份应该算是一个常用的功能。下面举一例,介绍其实现过程。单击Delphi工具栏上的“New Form”按钮,新建一窗体,在该窗体上添加1个Table组件、1个DataSource组件、1个DBGrid组件和2个Button组件,设置数据库之间的连接关系,并置Table的Active属性为True。窗体的布局设计如图7所示。
图7 窗体布局示例
然后在“备份表”按钮的OnClick事件中添加代码:
Procedure TForm1.Button1Click(Sender: TObject);
begin
QuickCopyTable(table1,'c:/a.dbf',true);
ShowMessage('数据表备份已完成')
end;
其中所引用的QuickCopyTable方法的代码如下所示。
Procedure QuickCopyTable(T: TTable; DestTblName: string; Overwrite: Boolean);
var
DBType: DBINAME;
WasOpen: Boolean;
NumCopied: Word;
begin
WasOpen := T.Active; //保存表的Active属性值
if not WasOpen then T.Open; //检查表是否打开
Check(DbiGetProp(hDBIObj(T.Handle),drvDRIVERTYPE,@DBType, SizeOf(DBINAME), NumCopied)); //获得驱动程序类型字符串
Check(DBICopyTable(T.DBHandle, Overwrite, PChar(T.Tablename),
DBType, PChar(DestTblName))); //复制表
T.Active := WasOpen; //恢复表的Active属性值
end;
最后,要在单元文件的Uses备份添加dbierrs库文件。此时单击Delphi工具栏上的“Run”按钮或按下F9键,运行程序,单击“备份表”按钮,如图8所示。所备份的数据表a.dbf内容,如图9所示。
图8 运行结果窗口
图9 备份的a.dbf文件的内容
1.1.8 如何使用过滤器
在操作数据库时,经常需要对数据进行筛选过滤。例如在第四章学生学籍管理系统的案例中,有一个名为jiben.dbf的数据表,它具有XH、XM、CSRQ等多个字段,如果只想查看班级编号为001的学生记录,就需要对数据表中的信息进行过滤。下面是常用的对数据表信息进行过滤的方法:
1. 利用TTable和TQuery的Filter属性
n 在设计时设置Filter属性
例如设置Filter为(bjbh=’001’),然后改变Filtered属性为True。此时将只能看到对应的bjbh(班级编号)字段内容为’001’的记录。另外设置Filter时可以使用的操作符有:<、>、<=、>=、=、<>、AND、OR、NOT。
n 在程序运行期间进行动态过滤
要在程序运行时改变Filter属性,包括两种情况。一是操作符右边为常量,例如语句(table1.Filter:='bjbh'+'='+'001';);二是操作符右边不为常量,可能是通过一个变量指定的值,或由一输入框给出的值。此时需要使用Format函数。其代码形式为(Table1.Filter:=Format('bjbh'+'='+'%S',[bjbhvalue]);)其中bjbhvalue为已经赋值的一个字符串变量,也可以为其他形式如Edit1.Text。
2. 用ApplyRange筛选数据集的记录
Delphi的DBDEMOS数据库中有一个Customer.db数据表,如果我们想查看顾客号从1000到3000之间的顾客记录。可使用ApplyRange, SetRangeStart, SetRangeEnd过程来完成,其代码为:
Table1.SetRangeStart;
Table1[‘CustNo’]:=1000;
Table1.SetRangeEnd;
Table1[‘CustNo’]:=3000;
Table1.ApplyRange;
需要说明的是该过程只适用于索引的字段。
3. 用OnFilterRecord事件过滤
例如下面的代码:
Procedure TForm1.Table1FilterRecord(DataSet:TDataSet;varAccept:Boolean);
begin
Accept:=DataSet[‘bjbh’]=’001’;
end;
4. 用Query组件的SQL语句
n SQL语句中不包含变量和参数
Select * from Customer Where CustNo>=1000 and CustNo<=3000
n SQL语句中包含参数
Select * from Customer Where CustNo>=:p1
可在程序中给参数p1赋值,读者可参阅前面讲述的案例程序。
n SQL语句中包含变量
此时向Query组件添加SQL语句的代码如下所示:
Query1.Close;
Query1.SQL.Clear;
Query1.SQL.Add(Format(‘Select * from Customer’+’’+’where CustNo=’+’%S’, [CustNoValue]));
Query1.Open;
1.2 关于字符串操作
在程序开发过程中,经常要涉及对字符串的操作。Delphi提供了一个TStrings类,用来完成存储和操作字符串,它的主要行为有:
1. 在列表中添加或删除字符串。
2. 重新排列字符串。
3. 在指定的位置存取字符串。
4. 从文件或流中读取及写入字符串
5. 为列表中的每一个字符串联系一个对象。
TStrings类的继承关系为:TObject-TPersistent
1.2.1 常用的字符串处理函数有哪些
表1列出了常用的字符串处理函数。
表1 常用字符串处理函数
函数名
| 语法
| 功能
|
AnsiCompareStr
| function AnsiCompareStr(const S1, S2: string): Integer;
| 用于比较两个大小写敏感的字符串
|
AnsiCompareText
| function AnsiCompareText(const S1, S2: string): Integer;
| 用于比较两个大小写不敏感的字符串
|
AnsiUpperCase
| function AnsiUpperCase(const S: string): string;
| 将字符串转换为全部大写
|
AnsiLowerCase
| function AnsiLowerCase(const S: string): string;
| 将字符串转换为全部小写
|
Appendstr
| procedure AppendStr(var Dest: string; const S: string); deprecated;
| 将给定字符串常量添加到目标字符串末尾
|
CompareStr
| function CompareStr(const S1, S2: string): Integer;
| 用于比较两个大小写敏感的字符串,其结果与区域设置无关
|
CompareText
| function CompareText(const S1, S2: string): Integer;
| 用于比较两个大小写不敏感的字符串,其结果与区域设置无关
|
Concat
| function Concat(s1 [, s2,..., sn]: string): string;
| 将一组字符串连接起来
|
Copy
| function Copy(S; Index, Count: Integer): string;
| 返回字符串的子串
|
Delete
| procedure Delete(var S: string; Index, Count:Integer);
| 从字符串中删除一个子串
|
Insert
| procedure Insert(Source: string; var S: string; Index: Integer);
| 在字符串的指定位置插入一个子串
|
Length
| function Length(S): Integer;
| 返回字符串中中字符的个数
|
Pos
| function Pos(Substr: string; S: string): Integer;
| 在字符串中搜索子串,返回的是索引值
|
LeftStr
| function LeftStr(const AText: AnsiString; const ACount: Integer): AnsiString;
| 返回从字符串左边开始指定长度的子串
|
RightStr
| function RightStr(const AText: AnsiString; const ACount: Integer): AnsiString;
| 返回从字符串末尾向前指定长度的子串
|
InttoStr
| function IntToStr(Value: Integer): string;
| 将整数转换为字符串
|
StrtoInt
| function StrToInt(const S: string): Integer;
| 将字符串转换为整数
|
LowerCase
| function LowerCase(const S: string): string;
| 转换为小写
|
UpperCase
| function UpperCase(const S: string): string;
| 转化为大写
|
Val
| procedure Val(S; var V; var Code: Integer);
| 将字符串的值转换为其数字表示式
|
1.2.2 TStrings对象的属性和方法有哪些
TStrings的一些重要属性和方法如下所示。
Count:该属性定义列表中字符串的数量。
Strings:该属性表示由参数Index指定位置的字符串。0表示第一个字符串,1表示第二个字符串,依此类推。
Text:TStings对象的文本,它包含一些由回车和换行符分开的字符串。
Add方法:该方法在字符串列表的末尾添加一字符串。在调用Add方法加入字符串之后,再返回新字符串的索引值。
AddObject方法:该方法向字符串列表中加入一个字符串及与它相联系的对象。调用AddObject方法之后将返回新字符串和对象的索引值。
Append方法:该方法将在字符串列表中添加一字符串,它与Add方法一样,但不返回值。
Clear方法:该方法将清空字符串列表。
Delete方法:删除指定的字符串。
Destroy方法:析构函数,毁坏一个TStrings对象实例。
IndexOf方法:function IndexOf(const S:string):integer; 该方法的功能是返回字符串S在字符串列表中的索引值。调用IndexOf函数返回的是第一次发现字符串S的位置。其返回值也以0作为起点,若返回0则表示是第一个字符串,返回1表示是第二个字符串,依此类推。如果指定的字符串S不在列表中,则返回-1。需要指出的是,若S在字符串列表中出现的次数大于1,则IndexOf方法返回的是第一次发现的位置值。下面介绍一个IndexOf方法的应用实例。
单击Delphi工具栏上的“New Form”按钮,新建一窗体,在该窗体上添加1个FileListBox组件、1个DirectoryListBox组件和2个Label组件,完成各个组件的属性设置及布局之后,在DirectoryListBox组件的OnChange事件中添加如下所示的代码。然后按F9键执行程序,其结果如图10所示。
图10 执行结果窗口
Procedure TForm1.DirectoryListBox1Change(Sender: TObject);
begin
filelistbox1.Directory:=directorylistbox1.Directory;
if filelistbox1.Items.IndexOf('regedit.exe')>-1 then
showmessage('当前是Windows目录');
end;
Insert方法:该方法在指定位置插入一字符串。其参数Index为给定的位置索引值,参数S为要插入的字符串。
LoadFromFile方法:调用该方法将使用指定的文件填充文本。其参数FileName用来定义文件名,文件中行与行之间以回车和换行符隔开。事实上,LoadFromFile方法是运用Add方法将文件中的每一行添加到字符串列表中的。
SaveToFile方法:与LoadFromFile方法相对应,该方法将列表中的字符串存储于文件中,其参数FileName用来定义文件名。
1.2.3 如何判断输入的字符串是电子邮件地址
选择“File|New Application”菜单命令,在默认建立从窗体中添加1个Label组件、1个Edit组件和1个Bitbtn组件,完成各个组件的属性设置即布局之后,在Bitbtn组件的OnClick事件中加入如下所示的代码。
Procedure TForm1.BitBtn1Click(Sender: TObject);
begin
if isemail(Edit1.Text) then
ShowMessage('正确的邮件地址!')
else
showmessage('错误的邮件地址!');
end;
其中isemail函数的代码如下所示,可将其添加到单元文件的implementation部分。
Function IsEMail(EMail: String): Boolean;
var
s: String;
ETpos: Integer;
begin
ETpos:= pos('@', EMail); //获取@符号的位置
if ETpos > 1 then
begin
s:= copy(EMail,ETpos+1,Length(EMail)); //截取子串
if (pos('.', s) > 1) and (pos('.', s) < length(s)) then
Result:= true
else
Result:= false;
end
else
Result:= false;
end;
按下F9键执行程序,其结果如图11和9-12所示。
图11 用户输入错误的邮件地址窗口
图11 用户输入正确的邮件地址窗口
1.2.4 如何获得字符串中汉字和英文的个数
Microsoft Word具有一项“字数统计”功能,可以统计出文本中汉字和英文的个数,如图12所示。
图12 Microsoft Word的字数统计功能
其设计思路就是通过把字符转换为ASCII码数值来进行判断,Ord函数就可以把字符转换为对应的ASCII码数值,其中值为33-126为键盘可使用字符,值127以上的为未知字符,即汉字。下面完成一个示范程序,选择“File|New Application”菜单命令,在默认的窗体中添加1个Memo组件、2个Label组件和2个Button组件,完成对各个组件的属性设置及布局之后,在“字数统计”按钮的OnClick事件中添加如下所示的代码。
Procedure TForm1.Button1Click(Sender: TObject);
var s:string;
I,e,c:integer;
begin
s:=memo1.text; //Memo的内容
e:=0;c:=0;
for I:=1 to length(s) do
begin
if (ord(s[I])>=33)and(ord(s[I])<=126) then
begin
inc(e);
label1.caption:='英文字数:'+inttostr(e);
end
else
if (ord(s[I])>=127) then
begin
inc(c);
label2.caption:='中文字数:'+inttostr(c div 2);
end;
end;
end;
按下F9键运行程序,其执行结果如图13所示。
图13 执行结果窗口
1.2.5 使用拼音首字母序列实现检索功能
在本书的考勤管理系统中,为了查询中文人名,我们在相应的数据表中添加了姓名编码字段,即提取员工姓名的首字母,作为其姓名编码。这样作的目的实质上就是为了方便查询功能的实现。本节提供的该问题解决途径在某种程度上可以进一步完善考勤信息管理系统案例。
事实上,解决这个问题的思路很简单。那就是要找出汉字表中拼音首字母分别为“A”到“Z”的汉字内码范围,这样对于要检索的汉字,只需要检查它的内码位于哪一个首字母的范围内,就可以判断出它的拼音首字母。
下面完成一个示范程序,选择“File|New Application”菜单命令,在默认的窗体中添加1个Label组件、1个Bevel组件、1个Edit组件和2个ListBox组件。完成对各个组件的属性设置及布局之后,在Edit组件的OnChange事件中添加如下所示的代码。
Procedure TForm1.Edit1Change(Sender: TObject);
var ResultStr:string;
begin
ResultStr:='';
listbox2.Items.Text := SearchByPYIndexStr(listbox1.Items, edit1.Text);
end;
这里所使用的SearchByPYIndexStr函数的代码如下所示,其功能是在字符串列表中检索符合拼音索引字符串的所有字符串。可将其添加到窗体单元文件的implementation部分。
Function SearchByPYIndexStr( SourceStrs:TStrings; PYIndexStr:string):string;
label NotFound;
var
i, j :integer;
hzchar :string;
begin
for i:=0 to SourceStrs.Count-1 do
begin
for j:=1 to Length(PYIndexStr) do
begin
hzchar:=SourceStrs[i][2*j-1]+ SourceStrs[i][2*j];
if (PYIndexStr[j]<>'?') and (UpperCase(PYIndexStr[j]) <>
GetPYIndexChar(hzchar)) then
goto NotFound;
end;
if result='' then
result := SourceStrs[i]
else
result := result + Char(13) + SourceStrs[i];
NotFound:
end;
end;
上面程序中引用的GetPYIndexChar函数用于获取指定汉字的拼音索引字母,如“杜”的索引字母是“D”。该函数的代码如下所示,也将其添加到窗体单元文件的implementation部分。
Function GetPYIndexChar( hzchar:string):char;
begin
case WORD(hzchar[1]) shl 8 + WORD(hzchar[2]) of
$B0A1..$B0C4 : result := 'A';
$B0C5..$B2C0 : result := 'B';
$B2C1..$B4ED : result := 'C';
$B4EE..$B6E9 : result := 'D';
$B6EA..$B7A1 : result := 'E';
$B7A2..$B8C0 : result := 'F';
$B8C1..$B9FD : result := 'G';
$B9FE..$BBF6 : result := 'H';
$BBF7..$BFA5 : result := 'J';
$BFA6..$C0AB : result := 'K';
$C0AC..$C2E7 : result := 'L';
$C2E8..$C4C2 : result := 'M';
$C4C3..$C5B5 : result := 'N';
$C5B6..$C5BD : result := 'O';
$C5BE..$C6D9 : result := 'P';
$C6DA..$C8BA : result := 'Q';
$C8BB..$C8F5 : result := 'R';
$C8F6..$CBF9 : result := 'S';
$CBFA..$CDD9 : result := 'T';
$CDDA..$CEF3 : result := 'W';
$CEF4..$D188 : result := 'X';
$D1B9..$D4D0 : result := 'Y';
$D4D1..$D7F9 : result := 'Z';
else
result := char(0);
end;
end;
完成之后,按下F9键运行程序,用户在编辑框中输入人名的拼音首字母后,在ListBox2中会自动检索对应的姓名,如图14所示。
图14 执行结果窗口
1.3 关于Delphi的VCL
OOP系统开发的核心是组件的使用,Delphi所提供的VCL类似于Visual C++的MFC和Borland C++的OWL类库,但C++的类库在可视化程序设计、易用性等方面远不如VCL库。用户使用VCL库可以快速制作系统交互界面,实现本地和远程数据库的存取,设计应用程序模板和ActiveX控件等。本节主要介绍一些有关VCL的技巧及应用。
1.3.1 深入了解RichEdit组件
RichEdit组件提供了一个标准的、丰富的文本操作界面,该组件允许用户输入不同字体属性和不同段落属性的文本。RichEdit组件与Memo组件非常相似,都可以编辑多行文本,但Memo编辑器中的文本只能有一种格式,而RichEdit组件中的文本却可以包含多种字体和颜色。
1.3.1.1 如何获得RichEdit组件的当前行号
使用RichEdit组件的lines.count属性可以得到该组件中文本的总行数,但不能知道光标当前所在行号,而要实现这个功能,则需调用em_ LineFromChar消息。
下面完成一个示范程序,选择“File|New Application”菜单命令,在默认的窗体中添加1个RichEdit组件和1个Button组件。完成对各个组件的属性设置及布局之后,在Button组件的OnClick事件中添加如下所示的代码。
Procedure TForm1.Button1Click(Sender: TObject);
var
CurrentLine:Integer;
begin
CurrentLine:=richedit1.Perform(EM_LineFromChar,richedit1.SelStart,0)+1;
Application.MessageBox(PChar('当前行号是'+IntToStr(CurrentLine)),'消息',mb_icon information);
end;
需要说明的是,由于EM_LineFromChar消息返回的行号中第一行为0,所以要在代码中加1。按下F9键运行程序,然后单击窗口中的Button按钮,其结果如图15所示。
图15 执行结果窗口
1.3.1.2 如何执行撤销操作
对于Memo组件来说,实现撤销功能是不需添加代码的,只需清除其Popupmenu属性,这样在程序运行时就能用鼠标右键激活一个快捷菜单,如图16所示,选择该菜单中的“撤销”命令即可。
图16 Memo的快捷菜单
而RichEdit组件却不能这样完成撤销操作。下面完成一个示范程序,选择“File|New Application”菜单命令,在默认的窗体中添加1个RichEdit组件和1个PopupMenu组件。双击PopupMenu组件,添加菜单项“撤销”,并在该菜单项的OnClick事件中添加如下所示的代码。
Procedure TForm1.N1Click(Sender: TObject);
begin
RichEdit1.Perform(EM_UNDO,0,0); //执行撤销操作
end;
需要说明的是,在执行撤销操作之前还要检查是否允许撤销,从而开启或关闭快捷菜单中的“撤销”命令,因此需在RichEdit1的OnChange事件中添加如下所示的代码。
Procedure TForm1.RichEdit1Change(Sender: TObject);
begin
N1.Enabled:=RichEdit1.Perform(EM_CANUNDO,0,0)<>0;
end;
按下F9键运行程序,然后右键单击窗口中的RichEdit区域,弹出如图17所示的快捷菜单,选中部分文本,按下Delete键,此时再右键单击窗口中的RichEdit区域,将弹出如图18所示的快捷菜单,此时“撤销”命令已激活,可以完成相应功能。
图17 “撤销”命令失效 图18 “撤销”命令激活
1.3.2 如何调整Memo中Tab的距离
使用制表键Tab在文本编辑器移动是用户频繁使用的操作,在Microsoft Word中可以自定义Tab移动的距离,那么在Delphi中如何实现对文本编辑类组件如Memo中Tab距离的控制呢?下面完成一个示范程序,选择“File|New Application”菜单命令,在默认的窗体中添加1个Memo组件,然后在窗体的OnCreate事件中添加如下所示的代码。
Procedure TForm1.FormCreate(Sender: TObject);
var
DialogUnitsX : LongInt;
PixelsX : LongInt;
i : integer;
TabArray : array[0..4] of integer;
begin
Memo1.WantTabs := true;
DialogUnitsX := LoWord(GetDialogBaseUnits); //返回对话框的基本单位
PixelsX := 30; //可设置该值决定Tab移动距离
for i := 1 to 5 do begin
TabArray[i - 1] :=
((PixelsX * i ) * 4) div DialogUnitsX; //计算TabArry
end;
SendMessage(Memo1.Handle, EM_SETTABSTOPS,5, LongInt(@TabArray));
//发送设置Tab距离消息
Memo1.Refresh; //刷新Memo
end;
按下F9键运行程序,然后在Memo中使用Tab键,如图19所示,图中使用了不同的PixelsX值。
图19 PixelsX=20和50的执行结果窗口
1.3.3 如何使用TApplication类
前面我们已经用到过TApplication类的一些属性和方法,如Application.messagebox等,这里作一个汇总。
1. 检测当前Windows程序是否被激活
TApplication类有一个Active属性,该属性用来描述当前运行的程序是否被激活。可以使用如下所示的代码进行检测:
If Application.Active=False then
ShowMessage(’当前窗口没有被激活’);
2. 取得当前程序名
TApplication类的EXEName属性可以返回该可执行程序的完整文件名(包括路径)。其代码如下所示:
ShowMessage(Application.ExeName);
3. 更改程序最小化时的标题
使用TApplication类的Title属性。可以控制程序最小化时的标题,而窗口中标题栏的标题则是由Form的Caption属性决定的。另外,在本书第1章曾经提到在设计期通过使用“Project|Options”菜单命令来设置应用程序的标题,实质上也是修改TApplication类的Title属性。其代码如下所示:
Application.Title:=’程序的标题’;
Form1.Caption:=’窗口的标题’;
4. 标识程序的主窗口
Windows应用程序一般都有一个主窗口。可使用TApplication类的MainForm属性返回程序的主窗口。
5. 弹出提示框
本书前面章节已经提到了TApplication类的MessageBox 函数,这里不再赘述。
6. 控制窗口的尺寸
可以用Application的事件来调整窗口大小。主要是使用以下两个过程:
Application.Minimized;
Application.Restore;
前一个过程用来将程序的主窗口最小化,而后一个过程用来将最小化的窗口恢复到原来的尺寸。
7. 链接联机帮助文件
TApplication的CurrentHelpFile属性能够指定当前程序所用的帮助文件的文件名。这个属性经常与另一个方法联合在一起使用。通过它们的命令组合,就可以使系统弹出一个显示某主题的联机帮助文件。
例如:Application.HelpFile := '联机帮助文件名';
Application.HelpJump('联机帮助文件的主题’)
8. 终止程序
尽管可以使用关闭主窗口的方法终止程序,但更好的办法是使用Application的Terminate过程。
1.3.4 通用的VCL属性有哪些
很多组件都有共同的属性,应该说了解VCL的公共属性对于程序设计是非常重要的。表2列出了一些通用属性。
表2 VCL公共属性
属性
| 范围
| 含义
|
Action | 某些组件 | 指定与组件连接的活动对象 |
Align | 某些组件 | 确定组件的对齐方式 |
Anchors | 大部分组件 | 说明与组件连接的窗体位置点 |
Autosize | 某些组件 | 确定组件是否依据内容决定本身大小 |
BiDiMode | 所有组件 | 支持自右向左输入的语言 |
BorderWidth | 窗口组件 | 定义边框宽度 |
BoundsRect | 所有组件 | 定义运行期组件的边框矩形 |
Caption | 大部分组件 | 定义组件标题 |
ComponentCount | 所有组件 | 定义当前组件拥有的子组件数目(在运行期调用) |
ComponentIndex | 所有组件 | 定义当前组件中子组件的索引位置(在运行期调用) |
Components | 所有组件 | 定义当前组件拥有的子组件数组,可用索引值调用 |
Constraints | 所有组件 | 在更改组件大小时先哲组件的最大和最小尺寸 |
Color | 大部分组件 | 定义当前组件的表面颜色或背景颜色 |
Ctrl3D | 大部分组件 | 定义组件是否具有三维效果 |
Cursor | 所有组件 | 定义当前组件上的鼠标形状 |
DragCursor | 大部分组件 | 定义当前组件在拖动时的鼠标形状 |
DragKind | 大部分组件 | 若DragMode为自动,则可选择拖动还是停放 |
DragMode | 大部分组件 | 定义组件的拖放行为 |
Enabled | 所有组件 | 定义组件是否激活 |
Font | 所有组件 | 定义组件内显示的文本字体 |
Handle | 窗口类组件 | 定义窗口句柄 |
Height | 所有组件 | 定义组件的高度 |
Hint | 所有组件 | 定义提示文本 |
Left | 所有组件 | 定义组件左上角的水平坐标值 |
Name | 所有组件 | 定义组件名 |
Owner | 所有组件 | 定义主组件 |
PopupMenu | 所有组件 | 定义用户右键单击时弹出的快捷菜单 |
ShowHint | 所有组件 | 定义是否激活提示文本 |
TabOrder | 窗口类组件 | 定义父组件中的切换顺序 |
Tag | 所有组件 | 用于存储定制的非定义数据的长整型变量 |
Top | 所有组件 | 定义组件左上角的垂直坐标 |
Visible | 所有组件 | 定义组件是否可视 |
Width | 所有组件 | 定义当前组件的宽度 |
1.3.5 通用的VCL方法有哪些
VCL的方法由函数和过程组成。表3列出了一些通用的组件对象方法。
表3 VCL 通用方法
属性
| 含义
|
BeginDrag | 开始手工拖动 |
BringToFront | 将组件置于其他组件前 |
CanFocus | 确定是否允许组件接收焦点 |
ClientToScreen | 将客户区坐标转换为屏幕坐标 |
ContainsControl | 确定某组件是否属于当前组件 |
Create | 建立组件的实例 |
Destroy | 析构函数,毁坏实例 |
Dragging | 说明组件是否被拖动 |
EndDrag | 终止手工拖动 |
ExecuteAction | 激活与组件相连的操作 |
FindComponent | 返回Components数组中具有给定名称的组件 |
Focused | 确定组件是否具有焦点 |
Free | 从内存中清除对象 |
GetTexBuf | 检索组件的文本(或标题) |
GetTextLen | 返回组件的文本(或标题) |
Hide | 隐藏组件 |
InsertComponent | 向组件列表中添加新组件 |
Invalidate | 强制重新绘制组件 |
ManualDock | 手工激活停放 |
ManualFloat | 将停放组件设置为浮动组件 |
RemoveComponent | 从Components列表中删除一个组件 |
ScaleBy | 用指定的百分比设置组件的比例 |
ScreenToClient | 将屏幕坐标转换为客户区坐标 |
ScrollBy | 滚动显示组件内容 |
SendToBack | 将当前组件置于其他组件后 |
SetBounds | 改变组件的位置和大小 |
SetFocus | 赋予组件输入焦点 |
SetTextBuf | 设置组件的文本(或标题) |
Show | 显示组件 |
Update | 重新绘制组件 |
1.4 综合
本节列举了一些程序设计过程中经常遇到的问题解答,作为前面案例学习的补疑。
1. 在Delphi中如何调用外部的EXE文件
首先在单元文件的Uses语句中添加WinProcs,然后在需要调用外部EXE文件的地方添加代码:
begin
WinExec(......);
end.
WinExec为API函数,其语法为:UINT WinExec(LPCSTR lpCmdLine, UINT uCmdShow );
2. 如何启动控制面板中的各项设置功能
在本书第3章的案例中曾经使用WinExec这个API函数启动了控制面板中的“系统信息”功能,而启动控制面板中的其他选项功能,如“添加/删除程序”、“显示”等,也可以使用如下所示的语句来完成(以中文Windows 2000操作系统为例)。
…
var x:cardinal; //定义变量
begin
//启动“控制面板”
x:=winexec('rundll32.exe shell32.dll,Control_RunDLL',9);
//启动“辅助功能选项”的“键盘”功能
x:=winexec('rundll32.exe shell32.dll,Control_RunDLL access.cpl,,1',9);
//启动“辅助功能选项”的“声音”功能
x:=winexec('rundll32.exe shell32.dll,Control_RunDLL access.cpl,,2',9);
//启动“辅助功能选项”的“显示”功能
x:=winexec('rundll32.exe shell32.dll,Control_RunDLL access.cpl,,3',9);
//启动“辅助功能选项”的“鼠标”功能
x:=winexec('rundll32.exe shell32.dll,Control_RunDLL access.cpl,,4',9);
//启动“辅助功能选项”的“常规”功能
x:=winexec('rundll32.exe shell32.dll,Control_RunDLL access.cpl,,5',9);
//启动“添加/删除程序”的“添加新程序”功能
x:=winexec('rundll32.exe shell32.dll,Control_RunDLL Appwiz.cpl,,1',9);
//启动“添加/删除程序”的“添加/删除Windows组件”功能
x:=winexec('rundll32.exe shell32.dll,Control_RunDLL Appwiz.cpl,,2',9);
//启动“添加/删除程序”的“更改或删除程序”功能
x:=winexec('rundll32.exe shell32.dll,Control_RunDLL Appwiz.cpl,,3',9);
//启动“显示”的“背景”功能
x:=winexec('rundll32.exe shell32.dll,Control_RunDLL desk.cpl,,0',9);
//启动“显示”的“屏幕保护程序”功能
x:=winexec('rundll32.exe shell32.dll,Control_RunDLL desk.cpl,,1',9);
//启动“显示”的“外观”功能
x:=winexec('rundll32.exe shell32.dll,Control_RunDLL desk.cpl,,2',9);
//启动“显示”的“设置”功能
x:=winexec('rundll32.exe shell32.dll,Control_RunDLL desk.cpl,,3',9);
//启动“Internet”的“常规”功能
x:=winexec('rundll32.exe shell32.dll,Control_RunDLL Inetcpl.cpl,,0',9);
//启动“Internet”的“安全”功能
x:=winexec('rundll32.exe shell32.dll,Control_RunDLL Inetcpl.cpl,,1',9);
//启动“Internet”的“内容”功能
x:=winexec('rundll32.exe shell32.dll,Control_RunDLL Inetcpl.cpl,,2',9);
//启动“Internet”的“连接”功能
x:=winexec('rundll32.exe shell32.dll,Control_RunDLL Inetcpl.cpl,,3',9);
//启动“Internet”的“程序”功能
x:=winexec('rundll32.exe shell32.dll,Control_RunDLL Inetcpl.cpl,,4',9);
//启动“Internet”的“高级”功能
x:=winexec('rundll32.exe shell32.dll,Control_RunDLL Inetcpl.cpl,,5',9);
//启动“区域选项”的“常规”功能
x:=winexec('rundll32.exe shell32.dll,Control_RunDLL Intl.cpl,,0',9);
//启动“区域选项”的“数字”功能
x:=winexec('rundll32.exe shell32.dll,Control_RunDLL Intl.cpl,,1',9);
//启动“区域选项”的“货币”功能
x:=winexec('rundll32.exe shell32.dll,Control_RunDLL Intl.cpl,,2',9);
//启动“区域选项”的“时间”功能
x:=winexec('rundll32.exe shell32.dll,Control_RunDLL Intl.cpl,,3',9);
//启动“区域选项”的“日期”功能
x:=winexec('rundll32.exe shell32.dll,Control_RunDLL Intl.cpl,,4',9);
//启动“区域选项”的“输入法区域设置”功能
x:=winexec('rundll32.exe shell32.dll,Control_RunDLL Intl.cpl,,5',9);
//启动“游戏选项”的“控制器”功能
x:=winexec('rundll32.exe shell32.dll,Control_RunDLL Joy.cpl,,0',9);
//启动“游戏选项”的“控制器ID”功能
x:=winexec('rundll32.exe shell32.dll,Control_RunDLL Joy.cpl,,1',9);
//启动“鼠标”功能
x:=winexec('rundll32.exe shell32.dll,Control_RunDLL Main.cpl',9);
//启动“声音和多媒体”的“声音”功能
x:=winexec('rundll32.exe shell32.dll,Control_RunDLL Mmsys.cpl,,0',9);
//启动“声音和多媒体”的“音频”功能
x:=winexec('rundll32.exe shell32.dll,Control_RunDLL Mmsys.cpl,,1',9);
//启动“声音和多媒体”的“硬件”功能
x:=winexec('rundll32.exe shell32.dll,Control_RunDLL Mmsys.cpl,,2',9);
//启动“电话和调制解调器选项的”的“调制解调器”功能
x:=winexec('rundll32.exe shell32.dll,Control_RunDLL Modem.cpl',9);
//启动“扫描仪和照相机”功能
x:=winexec('rundll32.exe shell32.dll,Control_RunDLL Sticpl.cpl',9);
//启动“系统”的“常规”功能
x:=winexec('rundll32.exe shell32.dll,Control_RunDLL Sysdm.cpl,,0',9);
//启动“系统”的“网络标识”功能
x:=winexec('rundll32.exe shell32.dll,Control_RunDLL Sysdm.cpl,,1',9);
//启动“系统”的“硬件”功能
x:=winexec('rundll32.exe shell32.dll,Control_RunDLL Sysdm.cpl,,2',9);
//启动“系统”的“用户配置文件”功能
x:=winexec('rundll32.exe shell32.dll,Control_RunDLL Sysdm.cpl,,3',9);
//启动“系统”的“高级”功能
x:=winexec('rundll32.exe shell32.dll,Control_RunDLL Sysdm.cpl,,4',9);
//启动“日期和时间”的功能
x:=winexec('rundll32.exe shell32.dll,Control_RunDLL timedate.cpl',9);
//启动“电源选项”的功能
x:=winexec('rundll32.exe shell32.dll,Control_RunDLL Powercfg.cpl',9);
//启动“电话和调制解调器”的“拨号规则”功能
x:=winexec('rundll32.exe shell32.dll,Control_RunDLL Telephon.cpl',9);
if x=0 then
messagebox(0,'程序超出内存','错误',0);
if x=ERROR_BAD_FORMAT then
messagebox(0,'该程序是一个非法的Win32程序).','错误',0);
if x=ERROR_FILE_NOT_FOUND then
messagebox(0,'指定文件没找到','错误',0);
if x=ERROR_PATH_NOT_FOUND then
messagebox(0,'指定路径没找到','错误',0);
end;
3. 如何实现对数据库中字段数据的模糊查询
在SQL的查询命令Select语法中,用LIKE可以使用两个很特殊的通配符“%”和“_”(下划线)其中百分比符号可以匹配任何长度的字符串,而下划线只能匹配单个字符,如果能在数据库查询设计中灵活使用这两个通配符,就可以对数据表中的字段进行模糊查询。例如,要查询学生基本信息表(jiben.dbf)中姓名(XM)字段所有“王”姓学生:
Select * From jiben where xm like ‘王%’
若需要查询“王”姓学生且其姓名只有两个字时,可使用下面的语句:
Select * From jiben where xm like ‘王_’
若要查询email地址字段,以Q或L开头的记录,可使用下面的语句:
Select * From jiben where email like ‘[ql]%’
需要说明的是,如要查询百分比符号或下划线字符本身,则需要使用方客户将它们括起来。
4. Delphi有哪些快捷键
快捷键的使用能加快程序开发速度,节省程序设计人员的时间。
n 对窗体中组件的操作
Ctrl+UP:向上移动当前组件(精确);
Ctrl+Left:向左移动当前组件(精确);
Ctrl+Down:向下移动当前组件(精确);
Ctrl+Right:向右移动当前组件(精确);
以上的快接键,再加上Shift进行组合,例如Ctrl+Shift+Right即可实现对组件位置的粗略调整。
Shift+UP:减小当前组件的高度;
Shift+Left:减小当前组件的宽度;
Shift+Down:增加当前组件的高度;
Shift+Right:增加当前组件的宽度;
n 对Object Inspector的操作
F11:切换到Object Inspector,若连续按F11则将实现在Object Inspector、Form和Code Editor之间的切换;
Ctrl+Down:下拉当前窗体的组件列表;
Ctrl+Enter:编辑带“…”的属性值(如组件的Font属性);
Alt+Down:下拉组件当前属性选项队列(如Align->alNone,alLeft,alRight等);
Ctrl+Tab:在属性列表及事件列表中切换。
5. 如何选择被组件覆盖了的窗体
当窗体中某个组件的Align属性设置为alClient时,此时选择窗体则变得较麻烦,事实上,可以通过使用以下的方式选择被组件覆盖了的窗体。
n 在Object TreeView中选择窗体;
n 按Esc键,一层一层选取,直到窗体被选中为止;
n 按Shift的同时单击鼠标左键,一步即可选中窗体;
n 按F11选定Object Inspector,然后切换到属性列表,再用Ctrl+Down快捷键。
6. 如何解决打开一个DBF数据表时出现的“Index not Found…”错误
当用户创建一个DBF表时,若使用了MDX格式的索引文件,那么DBF表的表头中的某个字节就自动设置了一个Flag。下次试图重新打开这个DBF表的时候,BDE会自动识别这个Flag,从而试图打开相应的MDX文件,若相应的MDX文件不存在,则会产生运行期错误。解决的方法是将Flag处的值置零。下面完成一个示范程序,选择“File|New Application”菜单命令,在所创建的默认窗体中添加1个Table组件和1个Button组件,该窗体的单元文件代码如下所示。
unit Unit1;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics,
Controls, Forms, Dialogs, StdCtrls, DB, DBTables, Grids, DBGrids;
type
TForm1 = class(TForm)
Table1: TTable;
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
const
TheTableDir = 'c:/temp/';
TheTableName = 'jiben.dbf';
procedure RemoveMDXByte(dbFile: String);
const
Value: Byte = 0;
var
F: File of byte;
begin
AssignFile(F, dbFile);
Reset(F);
Seek(F, 28);
Write(F, Value);
CloseFile(F);
end;
procedure TForm1.Button1Click(Sender: TObject);
//试图打开一个表,若不存在相应的MDX文件,将对表文件进行处理,并尝试再次打开。
begin
try
Table1.DatabaseName :=TheTableDir; //设置表路径
Table1.TableName := TheTableName; //设置表名
Table1.Open; //打开表
except
on E:EDBEngineError do
//若未发现MDX文件,将返回以下错误信息
if Pos('Index does not exist. File', E.Message)>0 then begin
//询问用户是否继续
MessageDlg('MDX file not found. Attempting to open
without index.', mtWarning, [mbOk], 0);
RemoveMDXByte(TheTableDir + TheTableName); //从表头中移去相应标志
PostMessage(Button1.Handle, cn_Command, bn_Clicked, 0);
end;
end;
end;
end.
7. 如何实现界面色彩渐变效果
界面色彩渐变效果是通过用渐变的画刷在Canvas上绘制依次相邻的矩形块实现的。下面完成一个示范程序,选择“File|New Application”菜单命令,在默认的窗体中添加1个Button组件,然后在按钮的OnClick事件中添加如下所示的代码。
Procedure TForm1.Button1Click(Sender: TObject);
var i,j:Integer;
Dct:TRect;
begin
j:=Form1.height; //获得窗体高度
for i:=0 to 255 do
begin
Canvas.Brush.Color:=RGB(255,0,i); //设置画刷颜色
Dct:=Rect(i*2,0,(i+1)*2,j); //定义绘制的矩形区域
Canvas.FillRect(Dct); //填充颜色
end;
end;
按下F9键运行程序,单击窗体中的Button按钮,此时窗体将显示由左到右红色渐变的效果。
8. 如何实现图形整体的拉出效果
其设计思路很简单,就是动态的改变图形区域的大小。需要注意的是要设置图形组件的Stretch属性为True。下面完成一个示范程序,选择“File|New Application”菜单命令,在默认的窗体中添加1个Image组件和1个Timer组件,并设置Image组件的Width属性为0,Timer组件的Interval属性为200,Enabled属性为True。然后在Timer组件的OnClick事件中添加如下所示的代码。
Procedure TForm1.Timer1Timer(Sender: TObject);
begin
Image1.width:=Image1.width+10; //设置增量
if image1.width=300 then //图形整体拉出完毕
Timer1.Enabled:=FALSE;
end;
按下F9键运行程序,则窗口中的图形将从左至右逐渐拉出,如图20所示。
图20 执行结果窗口
9. Delphi编写打印程序的技巧有哪些
利用Delphi编写打印程序,下面这些技巧的掌握将大有裨益。
n 获取当前打印机的分辨率
Windows下的打印分辨率对打印程序有着至关重要的作用,若想获取打印机的分辨率,请在程序中加入以下语句:
ShowMessage('水平分辨率'+inttostr(GetDeviceCaps(printer.Handle,LOGPIXELSX)) +chr(13)+'垂直分辨率:'+inttostr(GetDeviceCaps(printer.Handle,LOGPIXELSY)));
n 将结果直接送到打印机
Delphi提供了两种打印方式,一是将结果输送到窗体,再调用窗体的Print方法将结果输送到打印机;二是将结果直接输送到打印机。建议采用第二种方式。
n 尽量不要使用AssignPrn
尽管AssignPrn简化了文本打印操作,使输出到打印机象输出到文件一样简单。但有时却很不方便,例如用户无法知道当前打印的行数,无法准确控制行距,无法灵活改变字体等。因此最好还是使用打印机的Canvas属性执行打印操作。
n 用打印机的点数做度量单位
若想使打印程序在任何打印机上都能正常地打印,就必须改变度量单位。因为若采用固定的度量,不同分辨率的打印效果是不同的。此时最好使用打印机的点数做为度量单位。利用如下所示的代码可以完成这个功能:
Var
PointX,PointY:integer;
Begin
PointX:=GetDeviceCaps(printer.Handle,LOGPIXELSX);
PointY:=GetDeviceCaps(printer.Handle,LOGPIXELSX);
printer.Canvas.rectangle(0,0,PointX*1,PointY*2)
end;
这样无论使用何种打印机,都能得到一个1英寸宽、2英寸高的矩形。
n 添加打印程序单元
尽管Delphi在生成窗体时会自动在USES部分加入许多程序单元,但打印程序单元(Printers)却没有自动添加,因此需用户手工添加该单元。
10. 如何防止多次执行程序
Windows下的很多程序可以多次执行,如“资源管理器”。但有时可能需要禁止程序副本的运行,即用户多次执行程序时,只会激活那个已执行的程序,而不会启动另一个副本。如何实现这一功能呢?可以在主窗体的OnCreate事件中添加如下所示的代码。
Procedure TForm1.FormCreate(Sender: TObject);
var
ZAppName: array[0..127] of char;
Hold: String;
Found: HWND;
begin
Hold := Application.Title;
Application.Title := 'OnlyOne' + IntToStr(HInstance); //暂时修改窗口标题
StrPCopy(ZAppName, Hold); //原窗口标题
Found := FindWindow(nil, ZAppName); //查找窗口
Application.Title := Hold; //恢复窗口标题
if Found<>0 then begin
//若找到则激活已运行的程序并结束自身
ShowWindow(Found, SW_RESTORE);
Application.Terminate;
end;
end;
11. 如何获得系统的内存信息
获得系统的内存信息,需使用Windows API函数GlobalMemoryStatus,Delphi中的语法如下所示:
Procedure GlobalMemoryStatus(var lpBuffer: TMemoryStatus); stdcall;
其中的TMemoryStatus结构为:
TMemoryStatus=record
dwLength:DWORD;
dwMemoryLoad:DWORD;
dwTotalPhys:DWORD;
dwAvailPhys:DWORD;
dwTotalPageFile:DWORD;
dwAvailPageFile:DWORD;
dwTotalVirtual:DWORD;
dwAvailVirtual:DWORD;
下面完成一个示范程序,选择“File|New Application”菜单命令,在默认的窗体中添加1个Memo组件和1个Button组件,然后在Button的OnClick事件中添加如下所示的代码。
Procedure TForm1.Button1Click(Sender: TObject);
var
MemoryStatus: TMemoryStatus;
begin
Memo1.Lines.Clear;
MemoryStatus.dwLength := SizeOf(MemoryStatus);
GlobalMemoryStatus(MemoryStatus);
with MemoryStatus do
begin
Memo1.Lines.Add(IntToStr(dwLength) +' Size of ''MemoryStatus'' record');
Memo1.Lines.Add(IntToStr(dwMemoryLoad) +'% memory in use');
Memo1.Lines.Add(IntToStr(dwTotalPhys) +' Total Physical Memory in bytes');
Memo1.Lines.Add(IntToStr(dwAvailPhys) +' Available Physical Memory in bytes');
Memo1.Lines.Add(IntToStr(dwTotalPageFile) +' Total Bytes of Paging File');
Memo1.Lines.Add(IntToStr(dwAvailPageFile) +' Available bytes in paging file');
Memo1.Lines.Add(IntToStr(dwTotalVirtual) +' User Bytes of Address space');
Memo1.Lines.Add(IntToStr(dwAvailVirtual) +' Available User bytes of address space');
end;
end;
按下F9键运行程序,单击“内存信息”按钮,则在Memo组件中将显示有关内存的信息,如图21所示。
图21 执行结果窗口
12. 如何屏蔽Alt+Esc、Alt+Tab及Alt+F4键
为了禁止用户在各个窗口间使用Alt+Esc或Alt+Tab键切换,可按以下步骤进行,首先设置窗体的FormStyle属性为fsStayOnTop,WindowState属性为wsMaximized。然后在窗体的OnCreate事件中添加如下所示的代码,其目的是向Windows发送一个屏幕保护程序正在运行的消息。
Procedure TForm1.FormCreate(Sender: TObject);
var
temp: Integer;
begin
SystemParametersInfo(SPI_SCREENSAVERRUNNING, 1, @temp, 0);
end;
在窗体的OnClose事件中添加如下所示的代码,用以清除屏幕保护程序运行的标志。
Procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
var
temp: Integer;
begin
SystemParametersInfo(SPI_SCREENSAVERRUNNING, 0, @temp, 0);
end;
而要实现屏幕关闭窗口的Alt+F4组合键,则只需在窗体的OnKeyPress事件中添加如下所示的代码。
Procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if (ssAlt in shift)and(key=115) then
key:=0;
end;
13. Delphi中的消息处理机制是怎样的
在Delphi中可自定义消息,并可直接处理消息。对于那些希望在程序中截获、过滤消息的用户来说了解Delphi中消息处理的机制非常重要。
n Delphi VCL中消息的传递
Delphi中每一个VCL组件如Button,都有一内在的消息处理机制。它的特点是组件类接收到某些消息并把消息发送给适当的处理方法,如果没有特定的处理方法,则调用缺省的消息处理句柄。其中mainwndproc是定义在Twincontrol类中的一个静态方法,不能被重载(Override)使用。它不直接处理消息,而是交由wndproc方法处理,并为wndproc方法提供一个异常处理模块。Mainwndproc方法声明如下:
procedure MainWndProc(var Message: TMessage);
Wndproc是在TControl类中定义的一个虚拟方法,由它调用dispatch方法进行消息的分配,wndproc方法声明如下:
procedure WndProc(var Message: TMessage); virtual;
而dispatch方法是在Tobject根类中定义的,其声明如下:
procedure Tobject.dispatch(varMessage);
传递给dispatch的消息参数必须是一个记录类型,而且这个记录中第一个入点必须是一个cardinal类型的域(field),它包含了要分配的消息的消息号码。例如:
type
Tmessage=record
Msg:cardinal;
wparam:word;
lparam:longint;.
result:longint;
end;
而Dispatch方法会根据消息号码调用组件的最后代类中处理此消息的句柄方法。如果该组件和它的祖先类中都没有对应此消息的处理句柄,Dispatch方法便会调用Defaulthandler方法。Defaulthandler方法是在TObject中定义的虚拟方法,其声明如下:
procedure Defaulthandler(varMessage);virtual;
TObject类中的Defaulthandler方法只是实现简单的返回而不对消息进行任何的处理。但可以通过对此虚拟方法的重载,在子类中实现对消息的缺省处理。对于VCL中的组件而言,其Defaulthandler方法会启动windows API函数Defwindowproc对消息进行处理。
n Delphi中的消息处理句柄
在Delphi中用户可以自定义消息及消息处理句柄。消息处理句柄的定义有如下几个原则:
1. 消息处理句柄方法必须是一个过程,且只能传递一个TMessage型参数。
2. 方法声明后要有一个Message命令,后接一个在0到32767之间的消息标号。
3. 消息处理句柄方法不需要用Override命令显式指明重载祖先的一个消息处理句柄,另外它一般在组件的protected或private区声明。
4. 在消息处理句柄中,一般先是用户自己对消息的处理,最后用Inherited命令调用祖先类中对应此消息的处理句柄(有些情况下可能正相反)。由于可能对祖先类中对此消息的处理句柄的名称和参数类型不清楚,所以调用命令Inherited可以避免这样的麻烦,同样如果祖先类中没有对应此消息的处理句柄,Inherited就会自动调用Defaulthandler方法。
一般说来,消息处理句柄采用以下方法声明:
procedure Mymsgmethod(varmessage:Tmessage); messageMsgtype;
同样用户也可以定义自己的消息,用户自定义消息应从WM_USER开始。下面举一个自定义消息和声明消息处理句柄的例子:
constmy_paint=Wm_user+1;
type
Tmypaint=record
msgid:cardinal;
msize:word;
mcolor:longint;
msgresult:longint;
end;
type
Tmycontrol=class(TCustomControl)
protected
procedurechange(varmessage:Tmypaint);messagemy_paint;
……
end;
……
procedureTmycontrol.change(varmessage:Tmypaint);
begin
size:=message.msize; //设置组件尺寸属性
color:=message.mcolor; //设置组件颜色属性
……
inherited;
end;
n 过滤消息
过滤消息又称消息陷阱。在某些情况下,用户可能需要屏蔽某些消息,或者截获某些消息进行处理。通过上述介绍,可见过滤消息一般有3种途径:
(1) 重载组件继承的虚拟方法wndproc;
(2) 针对某消息编写消息处理句柄;
(3) 重载组件继承的虚拟方法Defhandler,在其中对消息进行处理。
14. 如何实现文件关联
建立文件关联可实现某种扩展名的文件由对应的应用程序打开,而实现文件关联的核心问题是对注册表的操作。所有文件的关联都保存在HKEY_CLASSES_ ROOT下,如图22所示。
图22 注册表中的文件关联键
因此要实现文件关联必须在HKEY_CLASSES_ROOT 中增加两个键值,一是和文件扩展名对应的类型说明;二是打开这种类型文件所需要执行的应用程序。下面完成一个示范程序,选择“File|New Application”菜单命令,在默认的窗体的OnCreate事件中添加如下所示的代码。事实上,有关文件的关联工作最好在主窗体的OnCreate事件中完成。
Procedure TForm1.FormCreate(Sender: TObject);
var
lphKey: HKEY;
sKeyName: string;
sKeyValue: string;
begin
sKeyName := 'myfile';
sKeyValue := '我的文档';
RegCreateKey(HKEY_CLASSES_ROOT,pchar(sKeyName), lphKey);
RegSetValue(lphKey, '', REG_SZ,pchar(sKeyValue), 0);
sKeyName := '.qjg';
sKeyValue := 'myfile';
RegCreateKey(HKEY_CLASSES_ROOT,pchar(sKeyName), lphKey);
RegSetValue(lphKey, '', REG_SZ,pchar(sKeyValue), 0);
sKeyName := 'myfile';
sKeyValue := 'c:/Winnt/NotePad.exe %1';
RegCreateKey(HKEY_CLASSES_ROOT,pchar(sKeyName), lphKey);
RegSetValue(lphKey, 'shell/open/command', REG_SZ,pchar(sKeyValue), MAX_PATH);
end;
按下F9键运行程序,然后使用Regedit打开注册表编辑器,可见新的键值已经建立,如图23所示。
图23 建立了新的键值
新建一个扩展名为qjg的文件,双击打开,则系统会自动使用Windows记事本程序打开这个文件。
15. 如何改变标题栏的字体
窗体标题栏的字体也可以修改的更加醒目,其方法是创建一个过程,截获系统的WM_NCPAINT信息,然后使用画布输出标题的字体。下面完成一个示范程序,选择“File|New Application”菜单命令,在窗体的单元文件中添加一个过程,如下所示。
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TForm1 = class(TForm)
private
{ Private declarations }
public
{ Public declarations }
procedure WMNCPAint(var Mes : TWMNCPaint); message WM_NCPAINT;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.WMNCPAint(var Mes : TWMNCPaint);
var
ACanvas : TCanvas;
begin
ACanvas := TCanvas.Create;
try
ACanvas.Handle := GetWindowDC(Form1.Handle);
with ACanvas do begin
Brush.Color := clActiveCaption;
Font.Name := '黑体';
Font.Size := 16;
Font.Color := clCaptionText;
Font.Style := [fsBold];
TextOut(GetSystemMetrics(SM_CYMENU) + GetSystemMetrics(SM_CXBORDER),
Round((GetSystemMetrics(SM_CYCAPTION) - Abs(Font.Height))/2) +1,
' 欢迎进入考勤管理系统!');
end;
finally
ReleaseDC(Form1.Handle, ACanvas.Handle);
ACanvas.Free;
end;
end;
end.
按下F9键,运行程序,则系统显示的窗口如图24所示。
图24 执行结果窗口
16. 窗体生成时的事件次序是怎样的
对于一般的SDI Form, 各事件的发生次序如下:
OnCreate
OnShow
在屏幕上显示窗口
OnActivate
OnPaint
对于MDI窗口,而且MDIChild 的第一个子窗口是在程序启动时,就出现在MDIForm中时,其各事件的发生次序如下:
主窗口的 OnCreate
子窗口的 OnCreate
子窗口的 OnShow
子窗口的 OnActivate
主窗口的 OnShow
在屏幕上显示主窗口及第一个子窗口
主窗口的 OnPaint
17. 如何显示密码编辑框中的密码
很多应用软件中的密码输入框,用户输入的字符是以诸如“*”等符号屏蔽掉的。尽管如此,但其内部还是以当初的字符表示的,因此可以使用Windows API函数将其显示出来。一般说来,Windows中的每一个窗口、组件都有自己 Name。而对于Form、Dialog Box及Message Box来说,其名称就显示在标题栏中;对于Edit、Button及Static Control,其名称则显示在本身占据的区域中。密码编辑框本身就是个Edit组件,尽管显示的是特殊字符,但其Name属性不变。Windows提供了两个API函数来获得这个Name:
int GetWindowTextLength(HWND hWnd); //得到名称的长度
int GetWindowText(HWND hWnd, LPTSTR lpString, int nMaxCount ); //得到名称
其中参数lpString表示存放名称的字符串地址,nMaxCount表示可复制的最大字符数。
下面完成一个示范程序,选择“File|New Application”菜单命令,在默认的窗体中添加1个Label组件、1个Edit组件和1个Button组件,并将Edit1的PasswordChar属性改为“*”,然后在Button1的OnClick事件中添加如下所示的代码:
Procedure TForm1.Button1Click(Sender: TObject);
var
Name:PChar;
Long:integer;
begin
Long:=GetWindowTextLength(Edit1.handle)+1;
GetMem(Name,Long);
GetWindowText(Edit1.handle,Name,Long);
label1.Caption:=String(Name);
FreeMem(Name,0);
end;
按下F9键,运行程序,在编辑框中输入字符,然后单击“查看密码”按钮,则用户输入的字符会在Label中显示出来,如图25所示。
图25执行结果窗口
18. 如何关闭Windows
可以使用API函数ExitWindowsEx控制Windows的开关,如关闭Windows,重新启动Windows等。该函数的语法为:
ExitWindowsEx(UINT uFlags,DWORD dwReserved);
首先定义常数:
const
EWX_FORCE=4; //关闭所有程序并以其他用户身份登录
EWX_LOGOFF=0; //重新启动计算机并切换到MS-DOS方式
EWX_REBOOT=2; //重新启动计算机
EWX_SHUTDOWN=1; //关闭计算机
运行时给Off赋值,让他等于EWX_SHUTDOWN或其他,然后调用以下语句即可实现关闭Windows。
ExitWindowsEx(off,0);
下面提供一个示范程序,其中所提供的AdjustToken过程主要用于在执行关闭Windows操作前判断用户的权限,即是否有权限关闭Windows,然后决定采用何种关闭方式。
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
procedure AdjustToken;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.AdjustToken();
var
hdlProcessHandle : Cardinal;
hdlTokenHandle : Cardinal;
tmpLuid : Int64;
tkpPrivilegeCount : Int64;
tkp : TOKEN_PRIVILEGES;
tkpNewButIgnored : TOKEN_PRIVILEGES;
lBufferNeeded : Cardinal;
Privilege : array[0..0] of _LUID_AND_ATTRIBUTES;
begin
hdlProcessHandle := GetCurrentProcess;
OpenProcessToken(hdlProcessHandle,
(TOKEN_ADJUST_PRIVILEGES Or TOKEN_QUERY),
hdlTokenHandle);
LookupPrivilegeValue('', 'SeShutdownPrivilege', tmpLuid);
Privilege[0].Luid := tmpLuid;
Privilege[0].Attributes := SE_PRIVILEGE_ENABLED;
tkp.PrivilegeCount := 1; // One privilege to set
tkp.Privileges[0] := Privilege[0];
AdjustTokenPrivileges(hdlTokenHandle,
False,
tkp,
Sizeof(tkpNewButIgnored),
tkpNewButIgnored,
lBufferNeeded);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
AdjustToken;
ExitWindowsEx((EWX_SHUTDOWN Or EWX_FORCE Or EWX_REBOOT), $FFFF);
end;
end.
按下F9键,运行程序,在系统显示的窗口中单击“关机”按钮,即可关闭系统,如图26所示。
图26 执行结果窗口
小 结
本部分可作为Delphi系统开发的疑难手册,它是很多程序设计人员心血的结晶。也许大科学家牛顿的话应该成为我们的座右铭“我之所以成功,是因为我站在巨人的肩膀上”。