分享到新浪微博 分享到QQ空间 打印

DELPHI基础教程

DELPHI基础教程

第七章 剪贴板和动态数据交换(二)


--------------------------------------------------------------------------------

7.3.5 控制服务器应用程序的执行 

        客户程序控制服务器应用程序的一个方面是:必要的时候客户程序可以启动服务器程序,并装载会话主题。

        而客户程序控制服务器应用程序更重要的一点是向服务器发送服务器承认的宏命令,来完成对服务器应用程序的各种操作。服务器到底支持哪些宏命令,可参阅服务器应用程序文档。

       发送宏命令要使用DDEClientConv的两个方法 ExecuteMacro和ExecuteMacroLines ,它们的语法如下: 

function ExecuteMacro(Cmd: PChar; WaitFlag: Boolean): Boolean;

function ExecuteMacroLines(Cmd: TStrings;WaitFlag: Boolean): Boolean; 

        Cmd是欲发送的宏命令字符串或宏命令字符串链表。WaitFlag决定了在DDE 服务器程序执行宏命令时客户程序的行为。如果WaitFlag设置为True,则在服务器宏命令执行完毕前,不允许对ExecuteMacro、ExecuteMacroLines、PokeData、PokeDataLines这些方法的成功调用,它们都不向服务器发送数据并返回False。如果WaitFlag设置为False,则调用的方法在第一个宏执行完毕前即试图向服务器发送数据。

        WaitFalg的设置也取决于服务器应用程序。一些应用程序当在第一个宏执行完之前就试图向它发送数据或命令时,可能导致第一个宏执行失败或导致不可预料的后果。具体情况可查阅服务器应用程序文档。

        函数返回值表示命令串是否被成功传输。而宏命令执行是否成功客户是无法检测到的。 

7.3.6 格式化文本 

        DDEClientConv有一个布尔属性FormartChars,用于决定是否格式化文本。所谓格式化文本是指从传输来的文本数据中过滤掉BackSpace(8)、 Tab(7) 、Linefeed(10) 、Return(13)等字符。括号内是字符的ASCII码。许多时候这些字符将导致DDE客户数据显示的混乱。

  FormatChars的缺省值是False。 

7.3.7 响应DDE事件 

        部件DDEClientConv有两个事件OnOpen和OnClose,分别在DDE 会话建立和中止时触发。部件DDEClientItem有一个OnChange事件。这一事件常用于DDE项目数据的转储和显示,如(7.3.1)节所示。

        在自动模式下,OnOpen事件在包含DDEClientConv部件的窗口创建时触发,或在调用SetLink方法时触发,OnClose事件在客户程序或服务器程序关闭时触发。

        在人工模式下,OnOpen事件在调用OpenLink 方法时触发,OnClose事件在调用ColseLink方法时触发。 

7.3.8 利用客户程序和Excel交换数据   

      下面我们建立一个DDE客户程序,并利用这一程序与Excel中的一个工作表交换数据。程序设计界面

      界面中包含一个DDE会话部件DDEClientConv1和DDE项目部件DDEClientItem1,用于建立和维护DDE联接;一个RadioGroup控件和其中的两个无线电按钮AutoRadio、ManualRadio,用于设置联接模式;一个GroupBox控件和其中的两个按钮RequestBtn和PokeBtn,用于控制数据的申请和发送,其中RequestBtn在自动模式下变灰;一个文本框Memo1用于保存DDE数据;一个按钮PasteBtn用于粘贴联接信息并建立DDE联接;另外一个按钮CloseBtn用于关闭系统。

        设计时把DDEClientConv1的FormatChars属性置为True,这样可以保留服务器传来数据的显示格式;ConnectMode保留ddeAutomatic的缺省设置。

        程序在类TForm1中定义了一个私有数据成员Automatic,用于标志联接模式;三个字符串数据成员DDEService、DDETopic、DDEItem用于记录联接信息。

窗口生成时进行变量和部件状态的初始化。 

procedure TForm1.FormCreate(Sender: TObject);

begin

RequestBtn.Enabled := False;

AutoRadio.Checked := True;

Automatic := True;

end; 

当联接模式改变时,程序进行相应的处理。

自动模式转换为人工模式: 

procedure TForm1.ManualRadioClick(Sender: TObject);

begin

if Automatic then

begin

RequestBtn.Enabled := ManualRadio.Checked;

DDEClientConv1.ConnectMode := ddeManual;

Automatic := False;

end;

end; 

人工模式转换为自动模式:

procedure TForm1.AutoRadioClick(Sender: TObject);

begin

if not Automatic then

begin

RequestBtn.Enabled := ManualRadio.Checked;

If (DDEService = '') or (DDETopic = '') then

begin

MessageDlg(' Can not Set Link.',mtWarning,[mbOK],0);

Exit;

end;

DDEClientConv1.SetLink (DDEService, DDETopic);

DDEClientItem1.DdeConv := DDEClientConv1;

DDEClientItem1.DDEItem := DDEItem;

DDEClientConv1.ConnectMode := ddeAutomatic;

Automatic := True;

end;

end; 

        当从自动模式转换到人工模式,只需要简单修改相应属性即可;而从人工模式转换到自动模式,则需要调用SetLink重新建立联接,否则往往会引发一个DDE异常。

联接的建立采用从剪贴板粘贴联接信息的方式,这是最具有灵活性的一种方法。

procedure TForm1.PasteBtnClick(Sender: TObject);

begin

if GetPasteLinkInfo (DDEService, DDETopic, DDEItem) then

begin

DDEClientConv1.SetLink (DDEService, DDETopic);

if Automatic then

begin

DDEClientItem1.DdeConv := DDEClientConv1;

DDEClientItem1.DDEItem := DDEItem;

end;

end;

end; 

        GetPasteInfo是 DDEMan库单元中定义的一个函数,用于检测剪贴板上是否有联接信息并返回相应的DDE服务、主题和项目。

         对于人工模式,必须由客户显式向服务器申请数据。在这种模式下DDE项目部件是多余的,接收到的DDE联接信息用一个字符串来记录。下面是实现代码。 

procedure TForm1.RequestBtnClick(Sender: TObject);

var

TheData: PChar;

begin

If DDEItem = '' then

begin

MessageDlg('Can not Request Data',mtWarning,[mbOK],0);

Exit;

end;

TheData := StrAlloc(79);

DDEClientConv1.OpenLink;

TheData := DDEClientConv1.RequestData(DDEItem);

DDEClientConv1.CloseLink;

if TheData <> nil then

Memo1.Text := StrPas(TheData);

StrDisPose(TheData);

end;

        OpenLink、CloseLink方法用于打开和关闭联接。RequestData方法向服务器申请数据并返回到一个PChar字符串中。字符串必须显式分配内存并在退出时释放。

        数据发送在不同联接模式下是不同的。对于人工模式,增加了联接的打开和关闭操作。程序清单如下。 

procedure TForm1.PokeBtnClick(Sender: TObject);

begin

If DDEItem = '' then

begin

MessageDlg('Can not Poke Data.',mtWarning,[mbOK],0);

Exit;

end;

if Automatic then

DDEClientConv1.PokeDataLines(DDEItem,Memo1.Lines)

else

begin

DDEClientConv1.OpenLink;

DDEClientConv1.PokeDataLines(DDEItem,Memo1.Lines);

DDEClientConv1.CloseLink;

end;

end; 

         打开Microsoft Office中的Excel,装入一个文件,把相关的单元选中,拷贝到剪贴板上。而后运行程序,按下Paste Link按钮,DDE联接就建立起来,相关单元中的数据显示在Memo1中。之后可以进行模式转换、数据申请、申请发送等一系列工作。运行后的屏幕显示如下图所示。

7.3.9 用客户程序控制程序管理器 

下面的例子用客户程序向程序管理器发送命令,用于创建程序组、程序项以及删除程序组。

        程序管理器提供了应用程序的DDE接口命令字符串,应用程序利用这些命令字符串可以实现以下的功能:

1.创建程序组

命令格式为:

CreateGroup(程序组名[,程序组所在的路径])

         程序组不存在时进行创建;如程序组存在则按照指定的路径激活。

2.删除程序组

命令格式为:

DeleteGroup(程序组名)

3.显示程序组

命令格式为;

ShowGroup(程序组名,显示标志)

        显示标志用于控制程序组在程序管理器中以极大、极小或正常方式显示。

4.重新装入程序组

命令格式为:

ReLoadGroup(程序组名)

        该命令使程序管理器先删除而后再重新装入一个已有的程序组。

5.向程序组中添加程序项

命令格式为:

       AddItem(命令行[,描述[,图标路径[,图标序号[,图标横坐标,图标纵坐标[,工作区目录[,热键[,是否最小化显示标志]]]]]]])

        命令行控制程序项的执行,可包括路径、参数等。其它参数分别对应在程序管理器中添加一个程序项时需要设置的参数和选项。它们都有缺省设置,因而是可选的。

6.替换程序组中的程序项

命令格式为:

ReplaceItem(程序项名)

        该命令删除一个程序项,并将所删除程序项的位置记录下来,以后通过AddItem在这个所记录的位置增加新项目。

7.从程序组中删除程序项

命令格式为:

DeleteItem(程序项名)

从当前活动程序组中删除一个程序项。

8.关闭程序管理器

命令格式为:

ExitProgram(是否保存程序组信息标志)

        从应用程序向程序管理器发送命令字符串的方法是基本一致的。为简便起见,在例程中只实现了其中仅包含一个字符串参数的情形,读者可以很容易作进一步的扩展。

        程序设计界面如图所示,包含一个DDE客户会话(DDEClientConv)部件和四个完成不同功能的按钮。

        DDEClientConv在设计时和程序管理器建立一个DDE会话,其中DDE服务器和DDE主题 都为PROGMAN。联接模式ConnectMode设置为ddeManual。

        我们把只有一个字符串参数的命令发送情况抽象出来,形成下面的SendMacro函数。 

function TForm1.SendMacro(Name: String;Command: String): Boolean;

var

Macro: String;

Cmd: array[0..255] of Char;

begin

Result := True;

if Name <> '' then

begin

Macro := Format('['+Command+'(%s)]', [Name]) + #13#10;

StrPCopy (Cmd, Macro);

DDEClient.OpenLink;

if not DDEClient.ExecuteMacro(Cmd, False) then

Result := False;

DDEClient.CloseLink;

end;

end; 

       过程首先利用Format函数形成宏字符串: 

Macro := Format('['+Command+'(%s)]', [Name]) + #13#10; 

        而后把Pascal类型的字符串拷贝到一个程序管理器可接受的PChar类型字符串中。

        DDE联接采用人工模式。首先调用OpenLink方法。而后调用ExecuteMacro方法发送命令,如失败则返回False。最后用CloseLink关闭联接。

        三个按钮CreateButton、AddButton、DeleteButton分别用于创建程序组、添加程序项、删除程序组。它们的程序实现大同小异,如下所示。

创建程序组: 

procedure TForm1.CreateButtonClick(Sender: TObject);

var

Name: String;

begin

Name := InputBox('Input Box','Input Group Name','');

if Name = '' then

MessageDlg('Group name can not be blank.', mtError, [mbOK], 0)

else

if SendMacro(Name,'CreateGroup') = False then

MessageDlg('Unable to create group.', mtInformation, [mbOK], 0);

end;

添加程序项: 

procedure TForm1.AddButtonClick(Sender: TObject);

var

Name: String;

begin

Name := InputBox('Input Box','Input Application full_Path name','');

if Name = '' then

MessageDlg('Application name can not be blank.', mtError, [mbOK], 0)

else

if SendMacro(Name,'AddItem') = False then

MessageDlg('Unable to Add Item.', mtInformation, [mbOK], 0);

end;

删除程序组: 

procedure TForm1.DeleteButtonClick(Sender: TObject);

var

Name: String;

begin

Name := InputBox('Input Box','Input Group Name to be Deleted','');

if Name = '' then

MessageDlg('Group name can not be blank.', mtError, [mbOK], 0)

else

if SendMacro(Name,'DeleteGroup') = False then

MessageDlg('Unable to create group.', mtInformation, [mbOK], 0);

end;

7.4 DDE服务器程序的实现 

  DDE服务器程序响应DDE客户的请求,一般地它包含了客户程序希望获取的数据。

         创建一个DDE服务器程序,必须要把一个DDEServerItem部件添加到窗体中。DDEServerItem的text或Lines属性包含了要联接的数据。一般地 DDEServerItem部件又和另一个文本控件相联系。当文本控件中的内容变化时则更新DDEServerItem 的text或Lines属性的值。下面的一段程序把DDEServerItem和一个列表框相联系。这一联系是在列表框的OnChange事件中实现。 

procedure Form1.OnListBoxChange(Sender: TObject);

begin

DDEServerItem1.Lines := ListBox1.Items;

end; 

        创建DDE服务器程序时也可以再加入一个DDEServerConv部件,并把两个部件利用DDEServerItem的ServerConv属性联系起来。此时DDE主题成为部件DDEServerConv的名称,而不是拥有DDEServerItem部件窗体的标题。

在下列情况下使用DDEServerConv部件成为必要:

        1.拥有DDEServerItem 部件窗体的标题可能在运行时改变或可能有其它窗体拥有同样的标题。在这种情况下DDE联接可能无法建立;

         2.DDE客户程序可能会向你的服务器程序发送一条宏命令。在这种情况下只有拥有一个DDEServerConv部件才能响应OnMacroExecute事件并执行相应的动作。 

7.4.1 和DDE客户程序建立联接 

        一般说来,建立DDE联接是客户程序的任务。但服务器程序可以把一个联接拷贝到剪贴板上供客户程序粘贴并建立DDE会话。步骤如下:

1.调用DDEServerItem部件的CopyToClipboard方法, 把Text(或Lines)属性的值和DDE联接信息拷贝到剪贴板上;

2.DDE客户程序插入联接的数据。一般地这是通过选择适当的命令(如Edit|Paste Special或Edit|Paste Link)来实现的。

7.4.2 响应DDE事件 

        部件DDEServerConv有三个事件:OnOpen、OnClose、OnExecuteMacro。前两个事件在DDE会话建立和终止时触发。同(7.3.7)中的介绍。

        OnExecuteMacro事件用于响应客户程序发送过来的宏指令。OnExecuteMacro事件处理过程有一个Msg参数,保存发送过来的指令串。用户可以在该过程中决定如何响应这些宏指令。

        DDEServerItem部件只有一个事件OnPokeData。这一事件用于响应客户程序发送来的数据。如果客户程序是Delphi程序,则客户程序调用了PokeData或PokeDataLines方法。在这一事件的处理过程中用户可以把发送来的数据保存到一个合适的地方。一般说来这应该就是DDEServerItem所联系的文本控件。

下面的程序把发送来的数据保存到ListBox中。

procedure Form1.OnDDEServerItemPokeData(Serder: TObject);

begin

ListBox1.Items := DDEServerItem1.Lines;

end; 

7.4.3 DDE服务器应用例程 

下面我们创建一个DDE服务器例程和一个相应的DDE客户例程。

DDE服务器例程可以完成的工作有:

1.把DDE联接信息拷贝到剪贴板上供其它程序使用;

2.利用一个TMemo部件为其它程序提供数据源;

3.接收客户程序发送来的数据;

4.根据客户程序发送来的宏指令改变自身的运行状态。

其中各部件的关键属性如下: 

DDESrvrForm.ActiveControl = Memo1

DDESrvrForm.Menu = MainMenu1

Bevel1.Align = alTop

Memo1.Align = alClient

DDETestItem.ServerConv = DDETestTopic 

通过设置Bevel1、Memo1的Align属性,可以保证窗口大小变化时仍能有较为美观的屏幕显示。

        Memo1是服务器的数据源,DDE项目部件DDETestItem通过Memo1的OnChange事件与Memo1 建立联系。 

procedure TDdeSrvrForm.doOnChange(Sender: TObject);

begin

if not FInPoke then

DDETestItem.Lines := Memo1.Lines;

end; 

        其中FInPoke是一个布尔类型的私有数据成员,用于标志程序是否在处理客户程序的数据发送。当数据是由客户发送过来转存到数据源时,则没有必要再把数据传给DDE项目部件。

        把联接信息拷贝到剪贴板,只需简单调用DDETestItem的CopyToClipboard方法。 

procedure TDDESrvrForm.CopyClick(Sender: TObject);

begin

DDETestItem.CopyToClipboard;

end; 

        这是通过菜单项Edit|Copy来调用的。

       接收客户程序发送来的数据,是在DDETestItem的OnPokeData事件处理过程中。在接收过程中改变FInPoke的值,以阻止数据的无效反送。 

procedure TDDESrvrForm.doOnPoke(Sender: TObject);

begin

FInPoke := True;

Memo1.Lines := DDETestItem.Lines;

FInPoke := False;

end; 

        在DDE会话部件DDETestTopic的OnExecuteMacro事件处理过程中处理客户发送来的宏指令。我们共定义了五种可以响应的宏指令:CopyDDE、Clear、WS_Normal、WS_MINIMIZED、WS_MAXIMIZED,分别用于拷贝联接信息、清除Memo1中的内容以及改变窗口显示状态。

procedure TDdeSrvrForm.doMacro(Sender: TObject;Msg: TStrings);

var

Cmd: String;

i: Integer;

begin

Cmd := '';

if Msg.Count = 0 then Exit;

for I := 0 to Msg.Count-1 do

begin

Cmd := Msg.Strings;

if UpperCase(Cmd) = 'COPYDDE' then

DDETestItem.CopyToClipboard

else if UpperCase(Cmd) = 'CLEAR' then

Memo1.text: = ''

else if UpperCase(Cmd) = 'WS_NORMAL' then

WindowState := wsNormal

else if UpperCase(Cmd) = 'WS_MINIMIZED' then

WindowState := wsMinimized

else if UpperCase(Cmd) = 'WS_MAXIMIZED' then

WindowState := wsMaximized

else

MessageDlg('Invalid Command',mtWarning,[mbOK],0);

end;

end; 

        下面的DDE客户程序,主要是为了验证上面的DDE服务器程序而设计的,但同时也提供了一个DDE客户程序设计的完整实例。

 

程序把接收到的DDE数据保存在一个TMemo类部件DDEDat中,而欲发送给服务器的数据和宏指令在另一个TMemo类部件PokeDat中输入。两个按钮PokeBtn、ExecuteBtn用于发送数据和宏指令。两个菜单项File|New Link和Edit|Paste Link用于根据用户的输入建立新联接和从剪贴板上粘贴DDE联接。

DDE联接的建立通过调用SetLink方法实现。

建立新联接的实现代码如下。 

procedure TFormD.doNewLink(Sender: TObject);

begin

DDEClient.SetLink (AppName.Text, TopicName.Text);

DDEClientItem.DdeConv := DDEClient;

DDEClientItem.DDEItem := ItemName.Text;

end; 

通过从剪贴板粘贴联接信息来建立联接的实现代码如下。 

procedure TFormD.Edit1Click(Sender: TObject);

var

Service, Topic, Item : String;

begin

PasteLink1.Enabled := GetPasteLinkInfo (Service, Topic, Item);

end;

procedure TFormD.doPasteLink(Sender: TObject);

var

Service, Topic, Item : String;

begin

if GetPasteLinkInfo (Service, Topic, Item) then

begin

AppName.Text := Service;

TopicName.Text := Topic;

ItemName.Text := Item;

DDEClient.SetLink (Service, Topic);

DDEClientItem.DdeConv := DDEClient;

DDEClientItem.DdeItem := ItemName.Text;

end;

end; 

在DDEClientItem的OnChange事件处理过程中把接收到的事件保存在DDEDat中。 

procedure TFormD.DDEClientItemChange(Sender: TObject);

begin

DDEDat.Lines := DDEClientItem.Lines;

end; 

数据发送的实现代码如下。 

procedure TFormD.doPoke (Sender: TObject);

var

DDECli : TDDEClientConv;

begin

DDECli := DDEClientItem.DdeConv;

if DdeCli <> nil then

DDECli.PokeDataLines (DDEClientItem.DDEItem, PokeDat.Lines);

end;

宏指令发送的实现代码如下。 

procedure TFormD.doMacro (Sender: TObject);

var

DDECli: TDDEClientConv;

Cmd: String;

begin

DDECli := DDEClientItem.DdeConv;

if DDECli <> nil then

begin

Cmd := PokeDat.Text + #13#10;

DDECli.ExecuteMacroLines (PokeDat.Lines, True);

end;

end; 

        运行以上两个程序,建立DDE联接。经测试,数据传输、数据发送和宏指令的发送与执行都达到预期要求。

7.4.4 小结 

        剪贴板和DDE是Windows下数据通信的两种方法。Delphi以简便、友好的方式实现了相应的功能,为用户编程提供了方便。一般说来,剪贴板多用于静态数据传输,而DDE用于动态数据交换、控制其它程序运行等场合。这些内容对于多用户环境下的程序开发是很重要的


想死你们了!

TOP

DELPHI基础教程

第八章 对象链接与嵌入(一)


--------------------------------------------------------------------------------

  对象链接和嵌入(Object Linking and Embeding)是一组服务功能,它提供了一种用源于不同应用程序的信息创建复合文档的强有力方法。 对象可以是几乎所有的信息类型,如文字、位图、矢量图形,甚至于声音注解和录像剪辑等。

  Windows附件组中的书写器是应用OLE的实例,使用单击“对象 | 插入”菜单项, 书写器弹出插入对话框,对话框中列出了多个OLE服务器程序,如公式编辑工具,绘图工具,报表生成工具。用户双击鼠标左键,可激活一个OLE服务器。在OLE服务器中可编辑OLE对象,当用户返回到书写器中时,在书写器文档中将出现OLE对象。

  Delphi支持OLE技术,Delphi1.0可以创建OLE应用程序,Delphi2.0可创建OLE自动化服务器和控制器程序。本章通过例程介绍对象链接与嵌入的基本概念,Delphi创建OLE对象的方法,OLE自动化的概念以及如何开发OLE自动化服务器和控制器。 

8.1 OLE简介 

8.1.1 OLE1.0和OLE2.0 

        迄今为止,有两种版本的OLE:OLE1.0和OLE2.0。当用户在OLE1.0 服务器中激活OLE对象,服务器程序在前台打开自己的窗体,并获得焦点。OLE窗体失去焦点,存在于单独的窗体之中。

  OLE2.0服务器采用“本地”(in place)激活方式。本地激活意味着服务器菜单与应用程序菜单要进行融合,服务器的状态条更换应用程序状态条,服务器的工具条更换应用程序工具条。OLE对象在应用程序窗体中进行编辑,但所有过程均由服务器处理。

  创建OLE对象的服务器决定了OLE的激活方式。如果一个OLE1.0的对象在OLE2.0 编译的应用程序中打开,它将采用OLE1.0的方式。 

8.1.2 链接与嵌入 

  链接对象的数据保存在OLE服务器创建的文件中,嵌入对象的数据保存在OLE应用程序中。

  链接对象必须以文件形式保存,只有对OLE服务器已经创建好的OLE对象, 才能进行OLE链接,链接的OLE对象文件可被OLE应用程序或其它程序进行修改,OLE 服务器和其它OLE应用程序也可以访问和修改OLE对象。对象数据保存在某一处,但可以被多个应用程序访问。

  Delphi应用程序可以得到OLE对象文件中的最新数据。当OLE 对象数据被应用程序修改时,这些变化将在所有包含该对象的其它应用程序中体现。

  嵌入对象保存在OLE应用程序中,其它应用程序不能访问该对象。只有在OLE应用程序中激活OLE对象才能对其进行编辑。嵌入的OLE对象不需要保存在文件中,所有数据都在应用程序中,这就确保了OLE数据不会被偶然地删除或修改。不足之处是应用程序的规模因为保存了OLE数据而增大了 。

  如果用户想保存对嵌入对象的修改,可以把OLE数据存入文件中,本章第3 节将详细讨论这个问题。

   表8.1 使用链接或嵌入的原则。

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

   何时使用链接         何时使用嵌入

───────────────────────────────

  想要对源对象进行修改及将   对源对象进行修改,并将这

  这些修改反映到其他与源对   些修改反映在一个特定的应

  象链接的应用程序或文本中   用程序或文本中

  源对象可能被多个OLE应    源对象不可能被一个OLE应

  用程序应用程序频繁修改    用程序频繁修改

  源对象的文件不会被频繁移   源对象的文件可能被频繁移

  动,且不会被删除     动,且不会被删除    

  对象很大,一般通过网络或   对象很小,或对象很大却无法

  电子邮件进行分配       通过网络或电子邮件进行分配

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 

8.1.3 设计状态OLE对象的创建 

  在Delphi中,可分别在设计状态或运行状态创建OLE对象,表8.2说明了两种状态创建对象的差别。 

表8.2 设计、运行状态OLE对象的创建

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  设计状态OLE对象的创建         运行状态OLE对象的创建

──────────────────────────────────────

对象保存在运行文件中,增加了所需 对象保存在一个文件中或只在运行时

编译的程序的规模       才有,减小了编译程序的规模 

开发者需在设计时访问OLE服务器   开发者不需要在设计时访问OLE服务器 

运行时OLE对象已经创建,减小了   运行时OLE对象已经创建,增加了运行

运行时间               时间 

OLE对象在设计运行时间可行性编辑   OLE对象只能在运行时编辑

应用程序的OLE对象数目在设计时已   应用程序可以在运行时创建新的OLE对

经确立                象

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 

  在设计状态,OLE服务器不能被本地激活,只能在自己的窗体内激活。但在运行状态,

只要OLE服务器支持本地激活,就可以使用这种方式。 

8.1.4 OLE类、文件、项目 

  OLE类决定创建OLE对象的服务器。有些应用程序需要创建多种类型的OLE对象,例如应用程序同时链接或嵌入公式、图片等。OLE类也决定OLE对象所包含的数据类型,链接或嵌入对象均要定义OLE类。

  OLE文件是包含OLE对象数据的源文件。链接对象必须使用对象文件,因为链接对象在文件中保存。如果应用程序从已存在的源文件中创建嵌入对象,也要使用OLE文件。例如,如果链接到QuattiPro笔记本的OLE对象TUTOR.WBI存储在D:\DFFICE\QPW目录下,则OLE文件就是D:\DFFICE\QPW\TUTOR.WBI。值得注意的是OLE文件只能为链接对象所定义,而对于嵌入对象,只需定义OLE类。

  OLE项目是代表链接或嵌入数据的OLE文件中的一部分。当应用程序希望OLE对象包含比OLE文件小的数据块时,则必须使用OLE项目。

  例如,在QuattiPro笔记本中,OLE对象链接了GasCosts的B4 到B5 范围的网格,OLE项目是$GasCosts;$B$4.$B$5。 

8.2 设计状态OLE对象的创建  

  Dephi可以在设计状态和运行状态中创建OLE对象。本节介绍设计状态OLE对象的创建。 

8.2.1 TOLEContainer部件 

  要创建OLE对象,需在窗体中加入OLE包容器部件。 应用程序部件包含链接或嵌入的对象。用该部件可显示在OLE服务器编辑的数据。部件的ObjClass,ObjDoc,ObjItem 属性分别定义OLE类、文件、项目。要定义OLE对象是否本地激活,使用InPlaceActive 属性。如果OLE对象可以本地激活,OLE服务器菜单将与OLE应用程序的菜单进行融合,GroupIndex属性的值将决定菜单融合情况。 

8.2.2 OLE对象创建的步骤: 

  1.在窗体中增加OLE包容器部件;

  2.在Object inspector中单击ObjClass或ObjDoc属性的省略按钮,将出现插入对象对话框;

        3.如果要插入的OLE 对象已存储在文件中,选择“Creat From File”,而后定义该对象的文件名和路径名。如果是链接对象,则选择链接检查框。 如果是嵌入对象,选择“Creat new”,并在对象类型列表框中选择OLE对象;

  4.选择OK按钮;

  如果是创建新对象,OLE服务器将激活,则可对OLE对象进行编辑,完成编辑后关闭OLE服务器。典型的例子是单击服务器中的“File”或“File|Update”菜单。

  5.此时ObjClass属性中包含了相应的值,如果OLE对象从已存在的文件中创建或插入一

个链接对象,ObjDoc属性包含了OLE文件。

  在设计对象状态时也可以粘贴OLE对象,其步骤如下:

  1.激活服务器应用程序,选择OLE包容器部件;

  2.在服务器中,将数据或对象拷贝到剪切板;

  3.进入Delphi集成开发环境,选择OLE包容器部件;

  4.在 Object inspector窗体中选择ObjItem属性的省略(…)按钮;

  5.在列表中选择OLE对象;

  6.选择“Paste"创建一个嵌入对象或选择"Pastelink"创建链接对象;

  7.选择OK。

OLE包容器部件在此时初始化。如果粘贴一个嵌入对象,ObjClass属性将包含适当的值。如果粘贴一链接对象,ObjClass,ObjDoc,ObjItem属性将全部定义。OLE 应用程序部件包含代表OLE对象的图片。

  如果OLE服务器程序支持OLE对象的拖放功能,则在设计状态从服务器中拖动对象至应用程序,应用程序将创建链接对象,具体步骤:

  1.激活服务器,并Delphi集成开放环境中选择要链接的对象;

  2.按隹鼠标左键拖动OLE对象至设计状态的窗体;

  3.松开鼠键释放OLE对象。

  窗体将创建OLE应用程序并进行初始化。 

8.3 OLE应用程序的开发 

  Delphi可以在设计状态和运行状态创建OLE对象,上一节介绍的是在设计状态如何创建OLE对象,这一节将通过例程介绍如何在运行状态创建OLE对象、粘贴对象、拖动对象,以及OLE 对象的文件操作。我们开发的 OLE.dpr是一个OLE应用程序的实例

8.3.1 OLE应用程序界面开发 

  OLE.dpr采用了多文档界面,父窗体有菜单,工具条,状态条,子窗体有一个OLE包容器部件,下面分别加以介绍。 

8.3.1.1 OLE应用程序的菜单 

  OLE应用程序的菜单与其它应用程序的主菜单大体一致,如果应用程序中有支持本地激活的OLE 2.0对象,则要进行菜单融合。查阅OLE 服务器的资料可知道服务器是否支持本地激活。

  OLE应用程序菜单的GroupIndex属性决定融合菜单的位置,即融合菜单是更换主菜单,还是插入至应用程序的主菜单中。

  OLE服务器,将融合三组菜单:Edit,View,Help,每组菜单分配了唯一的组索引值。在OLE应用程序中任何索引值为1,3,5的菜单组在菜单融合时被OLE服务器中具有相应索引值的菜 单更换。在本例程中,编辑菜单项在菜单融合时被服务器的"Edit"替换。如图8.3。 要想保存应用程序中的菜单,分配有异于1,3,5的索引值。

表8.3 融合后的菜单

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

菜单  索引值    功能描述       来源(OLE激活时)

────────────────────────────────

文件   0   使用文件和退出程序      OLE应用程序

Edit 1 编辑OLE对象         OLE服务器

对象   2 操作未激活的OLE对象     OLE应用程序

View 3 修改OLE对象的观测方式    OLE服务器

窗体  4 操纵窗体           OLE应用程序

Help 5 访问服务器在线帮助      OLE服务器

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 

8.3.1.2 OLE工具条和状态条 

  当OLE对象被本地激活时,OLE服务器将试图用自己的工具条和状态条替换OLE应用程序的。如果应用程序想要本地激活, 就应该在应用程序中编写相应的代码让服务器使用工具条和状态条。要做到这点,必须:

  ● 设置工具条和状态条

  ● 在应用程序中加入状态条

   通过修改面板部件的属性创建工具条和状态条。

  当OLE对象被本地激活时,面板或其他对齐控制将与OLE服务器程序进行协调。 这意味

OLE服务器可以替换OLE应用程序窗体中任何对齐控制,但锁定的控制不能被替换。例如,

如果面板的align属性是alTop,alleft,alBottom,alIngh时,控制未锁定,OLE服务器可以替换。要使应用程序的工具条、状态条不被替换,可将locked属性设置成真值。

  当OLE 对象被激活,OLE 服务器在状态条中显示有关信息时,OLE 应用程序部件的OnStatusLineEvent事件发生,一个文本字符会将从OLE服务器传至该事件句柄。 OnStatusLineEvent事件句柄的MSG参数接受文本字符。

以下代码用以状态条接收OLE服务器的信息:

procedure TOLEObjectForm.OleContainerStatusLineEvent(Sender: TObject;

Msg: String);

begin

OLEFrameForm.StatusBarPanel.Caption := Msg

end; 

8.3.2 插入OLE对象  

         运行状态时进行对象链接与插入也要用到插入对话框,Delphi中没有插入对话框部件,但可调用InsertOLEObjectDlg 函数来显示对话框。 

8.3.2.1 InsertOLEObjectDlg函数声明如下: 

function InsertOleObjectDlg(Form: TForm; HelpContext: THelpContext;

var PInitInfo: Pointer): Boolean;

  其中参数Form是拥有插入对话框的窗体,一般将拥有OLE包容器部件的窗体名字传给Form.

参数Helplontext为插入对象对话框定义在线帮助,如果应用程序没有在线帮助, HelpContext的值为零,对话框中将不出现帮助按钮。

  参数PInitInfo是一个无类型指针,该指针指向一个包含初始化OLE 部件信息的内部数据结构。InsertOLEObjectDlg修改这个指针以指向一个有效的数据结构,该结构包含了对话框列表中被选择的OLE 对象初始化信息。当该指针被使用后,应调用ReleaseOLEInitInfo过程释放初始化信息所占用的内存。

  当用户选择OK 按钮关闭插入对象对话框,InsertOLEObjectDlg 返回真值,并把 PInitInfo指向包含OLE对象的初始化信息的数据结构。 

8.3.2.2 初始化OLE包容器部件 

  为了使OLE包容器部件包含OLE对象,必须对部件进行初始化。 初始化主要是定义部件的OLE类。如果定义了OLE文件和OLE项目,初始化完成后,OLE 应用程序部件将包含OLE对象。

  调用InsertOLEObjetDlg函数可在其参数PInitInfo获得关于OLE对象初始化的信息时,把它传递给OLE包容器部件的PInitInfo属性,OLE包容部件的ObjClass,ObjDoc,ObjItem属性将被自动定义。

  初始化完成后,OLE对象被击活。OLE服务器将获得控制,用户可通过OLE服务器对OLE对象进行编辑。当程序冻结OLE对象,OLE包容器部件将包含一幅图像或位图代表OLE对象。定义OLE包容器部件的AutoActive属性可重新激活OLE对象,缺省情况下,双击OLE包容器部件可击活OLE对象。

  例程中初始OLE对象的代码如下:  

procedure TOLEObjectForm.InitializeOLEObject(Info: Pointer);

begin

OLEContainer.PInitInfo := Info;

ReleaseOLEInitInfo(Info)

end;

  该过程先将初始化指针传给OLE包容器部件的PInitInfo属性,而后释放其内存空间。

  当用户单击例程中的“编辑 | 插入”菜单项,将弹出插入对象对话框,选择对象类型后, OLE对象被激活,该过程的代码如下: 

  procedure TOLEObjectForm.InsertObject1Click(Sender: TObject);

var

Info: Pointer;

begin

if InsertOLEObjectDlg(OLEFrameForm, 0, Info) then

InitializeOLEObject(Info);

end;

8.3.3 冻结OLE对象 

  如果OLE对象是OLE 1.0服务器创建,对象将在OLE服务器中被击活,焦点和控制移到OLE服务器中。要冻结一个由OLE 1.0创建的对象选择"File | Exit"菜单项。

  如果OLE 2.0服务器支持本地激活,激活OLE对象后OLE服务器将进行菜单融合,并转换工具条和状态条。要冻结对象,只需在应用程序窗体中异于OLE包容器部件的任何地方单击鼠

标键即可。

  另一种冻结对象的方法是把OLE包容器部件的Active属性设置成假值。在例程中,“对象|冻结”菜单项实现冻结功能。代码如下: 

  procedure TOLEObjectForm.Deactivate1Click(Sender: TObject);

begin

OLEContainer.Active := False

end; 

8.3.4 粘贴OLE对象 

  一些OLE服务器允许用户把OLE对象复制到剪贴板,如果一个OLE对象复制到剪贴板上,OLE应用程序可通过初始化OLE包容器部件来粘贴OLE对象。 

8.3.4.1 粘贴对话框

       把OLE对象粘贴到OLE包容器部件,要使用粘贴对话框,Delphi 中没有粘贴对话框部件,但可用PasteSpecialDlg函数显示粘贴对话框。

  PasteSpecialDlg 函数声明如下: 

   function PasteSpecialDlg(Form :TForm;Const First:arrang; HelpConcert: THelpCOntext;var Forrmat : Word; var Hardle : THanlle var PInitInfo :Point ) : Boolean;  

PasteSpecialDlg参数定义如下:

  参数Form是拥有粘贴对话框的窗体,应把包含OLE包容器部件的窗体名字传递给Form。

        参数Format是注册对象格式的数组,每组格式是BOLEFormat类型的数组成员。例如应用程序可注册两种对象格式。为嵌入对象注册FEmbedClipFmt ,为链接对象注册FlinkClipFmt。

BOLEFormat 声明如下: 

  BOLEFormat: Record

fmtID : Word;

fmtName : array[0..31] of char;

fmtResultName : array[0..31] of char;

fmtMediun : BOleMedium;

fmIsLInkble : Bool;

end; 

        fmtID是对象的剪贴板格式ID号,fmtID 可以是标准的剪贴板格式:CF_TEXT,CF_BIFMAP。使用OLE 对象时, 需注册新的剪贴板格式来处理OLE 对象。Windows的API中 的RegisterClipbordFormat函数注册格式。

        fmtName表示是对象的名字,用以定义出现在粘贴对话框中列表框 内的对象名称。在例程中,把“%S”匹配给fmtName,OLE服务器自动地把格式化的名字代替“%S”参数。例如,如果OLE服务器是画笔,在程序运行时“Paintbrush Picture Object”将代替“%S”。

  fmtResultName,定义出现在粘贴对话框中结果检查框内的名字。在例程中, 把“%S”传给了fmtResultName。OLE服务器自动地把格式结果名称代替“%S”参数。例如,如果OLE服务器是画笔,程序运行时“Paintbrush Picture”将代替“%S”。

  fmtMedium是BOLEMedium类型,是Windows决定对象格式的数据类型。例如,OLE 联

接对象的格式是BOLE_MED_STREAM。OLE嵌入对象的格式是BOLE_MED_STORAGE。BOLEMedium函数可计算出需要的BOLEMedium类型。

  fmtIsLinkale决定对象格式是否可联连。联连对象的fmtIsLinkable为真值。嵌入对象的fmtIsLinkable为假值。

  参数HelpContext 为粘贴对话框定义在线帮助。如果应用程序没有在线帮助,HelpContext的值为零,对话框中将不出现帮助按钮。

  参数Form用以定义剪粘板上的格式,是由PasteSpecialDlg函数进行修改。因为使用粘贴对话框时,应用程序并不知道剪贴板的格式。因而用Format来处理剪贴板的数据。在本章例程中。 PasteSpecialDlg 函数把format 变量修改成FEmbedClipFmt 或FLinkClipFmt格式,这两种格式是在主窗体的OnCreate事件中定义的。如果剪贴板上的数据不是OLE对象,Format将被修改成其它类型的格式,如CF_TEXT等。

  参数Landle定义剪贴板上的数据句柄。由PasteSpecialDlg函数进行修改。 当剪贴板的数据类型不是OLE对象时,需用Handle参数访问剪贴板数据。Handle是句柄类型。

  参数PInitInfo是一个指向OLE对象初始化结构的指针。前面在讲述初始化OLE应用程序部件时也用到了这种指针。PasteSpecialDlg函数将修改PInitInfo指针以使其指向一个有效的数据结构。该结构包括了粘贴对话框中被选中的OLE对象的初始化信息。

  下面介绍粘贴对话框中的部件。

  ● 将剪贴板上的数据插入OLE应用程序,以实现对象嵌入,须选择"Paste";

  ● 在OLE服务器资源文件与OLE应用程序之间建立联连,以实现对象联连,须选择: "Paste Line;

  ● 要将闻连与嵌入的对象显示成图标,选择"Display As Icon"。若这个检查框被选中,改变图标("Chang Icon")按钮将显示通过这个按钮可改变OLE对象的缺省图标或标签。

  ● 如果数据不是注册的格式,"Paste","Paste link"选择键将变灰。 用户无法从剪贴板上粘贴数据。在本章例程中,剪贴板上的数据只能是FEmbedClipFmt(嵌入对象) 和FlinkClipFmt(链接对象)。

  ● 用户在列表框中选择数据类型。有时数据被解释成多种类型。例如在包含OLE服务器功能的字处理器中把文本复制到剪贴板中。应用程序可以以文本和OLE对象两种方式粘贴对象。列表框中出现的选择项由OLE服务器决定。

  用户在粘贴对话框中选择OK按钮,PasteSpecialDlg返回真值,关于OLE 应用程序的初始化信息贮存在PInitInfo所指向的结构中。 

8.3.4.2 在剪贴板中使用OLE对象 

  要把OLE对象粘贴到OLE应用程序中,必须用Windows的 RegisterClipboardFormat函数为链连对象、嵌入对象注册两种新的剪贴板格式。这些格式将在BOLEFormat记录的fmtIdt域中被用到。

  本章例程中, 程序在OnCreate事件中注册OLE对象的剪贴板格式,以下代码是主窗体的OnCreate事件: 

  procedure TOLEFrameForm.FormCreate(Sender: TObject);

begin

FEmbedClipFmt := RegisterClipboardFormat('Embedded Object');

FLinkClipFmt := RegisterClipboardFormat('Link Source');

Fmts[0].fmtId := FEmbedClipFmt;

Fmts[0].fmtMedium := BOLEMediumCalc(FEmbedClipFmt);

Fmts[0].fmtIsLinkable := False;

StrPCopy(Fmts[0].fmtName, '%s');

StrPCopy(Fmts[0].fmtResultName, '%s');

Fmts[1].fmtId := FLinkClipFmt;

Fmts[1].fmtMedium := BOLEMediumCalc(FLinkClipFmt);

Fmts[1].fmtIsLinkable := True;

StrPCopy(Fmts[1].fmtName, '%s');

StrPCopy(Fmts[1].fmtResultName, '%s');

RegisterFormAsOleDropTarget(Self, Fmts)

end; 

        程序传给RegistClipBroardFormat函数一个描述格式的参数,它返回一个Word类型的值。该值能唯一的辨识新注册的格式。FEmbdeClipFmt,FlinkClipFmt 是TOLEFormat类的私有数据成员。 声明如下:

  TYPE

TOLEForaneForm = Class(TForm)



private

FEmbedClipFmt: Word;

FLinkClipFmt: Word;

function CreateChild: TOLEObjectForm;

public

Fmts: array[0..1] of BOleFormat;

end; 

        在注册剪贴板格式后, 还必须定义OLE 格式才能进行对象粘贴。 每种格式定义在BOLEFormat记录中。 程序中可能注册标准剪贴板格式并用这种格式进行粘贴。例如:注册文本作为粘贴格式,将BOLEFormat记录为fmtId域定义为CF_TEXT,fmt Medium 域定义为BOLE_MED_HGLOBOL。 BOLEMediumCalc 函数可以根据定义的剪贴板格式计算出fmtMedium值。在本章例程中,程序注册了两种格式,一种是链接OLE对象的格式,另一种是嵌入OLE对象的格式。

  BOLEFormat类型定义在BOLEDefs单元中,BOLEMediumCalc函数定义在ToCtrl单元。因此主窗中的interface部分应加入这两个单元。 

  interface 

use…,BOLEDefs,ToCtrl,

  在粘贴OLE对象前,应用程序必须知道在剪贴板中是否有OLE对象。

  PasteSpecialEnabled函数可判断粘贴对话框是否有效。如果剪贴板上有Fmts定义的任何一种格式,PasteSpecialEnable将返回真值, 粘贴对话框才能成功地调用。反之调用粘贴对话框将不发生任何事件。

  以下代码实现“编辑|粘贴”菜单项的功能: 

procedure TOLEObjectForm.PasteSpecial1Click(Sender: TObject);

var

ClipFmt: Word;

DataHand: THandle;

Info: Pointer;

begin

if PasteSpecialEnabled(Self, OLEFrameForm.Fmts) then

if PasteSpecialDlg(Self, OLEFrameForm.Fmts, 0,

ClipFmt, DataHand, Info) then

InitializeOLEObject(Info)

end; 

只有在粘贴对话框有效时“编辑|粘贴”菜单才有效,以下代码实现此功能: 

  procedure TOLEObjectForm.Edit1Click(Sender: TObject);

begin

PasteSpecial1.Enabled := PasteSpecialEnabled(Self, OLEFrameForm.Fmts)

end; 

8.3.5 释放OLE对象 

  从OLE服务器拖动OLE对象并将其放在OLE应用程序是一种方便的对象链接与嵌入的方法。通过拖放操作,用户不需要使用插入对话框或粘贴对话框来定义OLE对象。而只需用鼠标键从OLE服务器中“抓”住OLE对象,拖至OLE应用程序,松开鼠标键,从而实现OLE对象的插入。 

8.3.5.1 注册OLE释放目标窗体 

  为了接收一个释放的OLE对象,必须有一个窗体在Windows中注册成OLE释放目标,用RegisterFormASOLEDropTarget函数可实现此功能。 

  RegisterFormASOLEDropTarger(Form : TFrom;Const Fmts: array of BOlefrom).

  其中Form是OLE对象的释放目标窗体,在本章例程中,将子窗体传递给Form参数。

  Fmts是对象格式的数组。它是BOLEFormat 类型的数组。 所有要释放的数据必须用Fmts数组中相应BOLEFormat元素注册。

  在本章例程中,注册的Fmts 数组与主窗体OnCreate事件 声明的数组相同, 即:联接对象格式和嵌入对象格式。如果想接收更多类型的释放数据,就必须在Fmts数组中加入其它元素。例如应用程序要接收释放的文本,Fmts需加第三个元素, 其fmtId 域为CF_TEXT,BOLEMedium域为BOLE_MED_HGLOBL.

拖放过程中不需要用BOLEFormat的fmtName,fmtResultName域,如果程序只进行拖放操作而不进行对象粘贴,可以不初始化两个域。

  在主窗体的OnCreate事件中可调用RegisterFormAsOLEDropTorget。 

procedure TOLEFrameForm,FormCreate(Sender : TObject);

begin…

  Register FormASOleDropTarget(Self,Fmts)

end; 


想死你们了!

TOP

DELPHI基础教程

第八章 对象链接与嵌入(二)

8.3.5.2 在应用程序中释放OLE对象 

  当一个对象释放到一个窗体,该窗体发生OnDragDrop 事件。该对象定义为TDragDropEvent方法中的Source参数,而TDragDropEvent 方法是用来处理OnDragDrop事件”。 如果Source 是一个OLE 对象, 那么它是TOLEDropNotify 对象的派生类型。 TOLEDropNotify对象有一个与OLE包容器部件PInitInfo属性相对应的PIniInfo属性。 如果一个OLE对象被释放。PInitInfo指向OLE对象的初始化信息结构。要实现释放功能。只需将TOLEDropNotify的PInitInfo属性赋给OLE包容器部件的PInitInfo属性。

  以下为处理OnDragDrop事件的代码: 

procedure TOLEFrameForm.FormDragDrop(Sender, Source: TObject; X,

Y: Integer);

var

NewChild: TOLEObjectForm;

begin

if Source is TOLEDropNotify then

begin

NewChild := CreateChild;

with Source as TOLEDropNotify do

NewChild.OLEContainer.PInitInfo := PInitInfo

end

end; 

注意不要用ReleaseOLEInitInfo释放分配给PInitInfo属性的内存。Delphi自动释放这块内存。 

8.3.6 文件中的OLE对象 

  在OLE应用程序中,要保存对OLE对象的修改,需将对象数据保存在文件中。 如果对象是链接的数据,Delphi将自动的保存在源文件中。当对象被修改时,文件中的数据自动修改。 如果对象是嵌入的,数据贮存在应用程序程序的窗体。要保存对嵌入对象的修改, 应用程序应把数据保存在特殊的OLE文件中。如果要对已存文件的对象进行编辑,应用程序必须从文件中装入OLE对象。

  OLE包容器部件的SaveToFile方法可保存对象: 

  OleCntainer1.SaveToFile('C: \SALEs.OLE'); 

  OLE包容器部件的loadFromFile方法可把文件中的对象装入OLE包容器部件。 

  OleContainer1.loadFromFile('C:\SALEs.OLE')

  本章例程使用了保存对话框和打开对话框来实现运行状态的对象保存和对象装入。

  在OLEObjectForm窗体加入保存对话框部件和打开对话框部件。其主要属性如表8.4: 

  表8.4 保存对话框的属性及取值:

━━━━━━━━━━━━━━━━━━━━━━━━

 属性        值

────────────────────────

  Name SaveAsDialog

DefaultExit ole

FileName .OLE

Filter OLE files (*.OLE)|*.OLE

━━━━━━━━━━━━━━━━━━━━━━━━ 

表8.5 打开对话框的属性及取值

━━━━━━━━━━━━━━━━━━━━━━━━━

  属性        取值

────────────────────────

  Name OpenDialog

DefaultExit ole

FileName .OLE

Filter OLE files (*.OLE)|*.OLE

━━━━━━━━━━━━━━━━━━━━━━━━━ 

  用户单击“文件|保存”菜单项实现OLE对象的保存。代码如下: 

procedure TOLEObjectForm.SaveAs1Click(Sender: TObject);

begin

if SaveAsDialog.Execute then

OLEContainer.SaveToFile(SaveAsDialog.Filename)

end; 

用户单击“文件|打开”菜单项实现对象文件装入: 

procedure TOLEFrameForm.Open1Click(Sender: TObject);

var

NewChild: TOLEObjectForm;

begin

f OpenDialog.Execute then

begin

NewChild := CreateChild;

NewChild.OLEContainer.LoadFromFile(OpenDialog.FileName)

end

end;

   8.4 OLE自动化 

  OLE自动化是Windows应用程序操纵另一个程序的一种机制。OLE 2.0提供了一种方法来集成应用程序,这就是应用程序之间的命令操作。

  利用OLE 2.0,程序员可以定义一组命令,使它们进入到其它程序中。这些命令可带参数。看起来很象应用程序在调用函数或过程一样。采用上述办法, 可以在人不参与的情况下,就能使得两个应用程序的相互作用。

  被自动化的程序称作自动化对象或自动化服务器, 操作或自动化其他程序的应用程序称为自动化控制器或自动化客户器。

  Delphi2.0完全支持OLE2.0的应用程序自动化,可以用Delphi 2.0编写自动化控制器和服务

器。在应用程序之间可编程的潜能是巨大的。用户可以创建宏或者其它命令, 使得某个应用程序能透过其它应用程序进行工作。已经存在的应用程序的宏语言很容易被扩展,它可以包括一组别的应用程序能够执行的命令和函数调用。

   现在介绍两个应用程序,其中MemoEdit.dpr 是多文档界面的文本编辑器,作为OLE自动化服务器,AutoFrom.dpr是自动化控制器。运行AutoForm前,在Delphi集成开发环境中单击菜单(run | parameters),Delphi弹出运行参数对话框,如图8.5,输入参数后运行状态如图8.6。AutoForm窗体的多个按钮。可对MemoEdit进行操作;如按Creat按钮,MemoEdit产生三个子窗体,如图8.7,按"AddText",子窗体将出现"This text was added through OLE Automation"的字符串“

MemoEdit包括三个单元:

  Mainfrom MDI主窗体

  EditFrom MDE子窗体和自动化类

  MemoAuto 应用程序自动化对象

  下面结合例程讲述OLE自动化的基本概念及开发。 

8.4.1 TAutoObject对象 

  TAutoObject 是Delphi自动化服务器中所有对象的基类,任何自动化对象都是从TAutoObject类派生出来的。

  OLE对象的定义与其它类的定义类似。它的automated部分象普通类的public部分,OLE控制器可引用在这部分声明的属性和方法。编译器把automated部分创建成OLE自动化对象的入口。但automated部分的代码有很多限制:

  ● 属性方法可以定义,但不能定义域;

  ● 所有属性、参数、函数类型必须是以下类型之一: 

  SmallInt,Integer,Single,Double,Currency,TDateTime,String,WordBool, Varint 

● 属性声明只能包括访问定义符(read and Write),其它定义符如index,stored,

default,odefault均不能使用;

  ● 访问定义符必须列出相应的方法标识符,不能使用域标识符;

  ● 支持数组类型;

  ● 不允许属性重载;

  ● 方法是可以是虚拟的,但不能是动态的,允许方法重载。

  在EditFrom单元中定义了TMemoDoc类: 

  type

TMemoDoc = Class(TAutoObject)

private

FEditForm : TEditForm;

funtion CretFileName : String;

funtion CretModiFied : WordBool;

procedure SetFileName(Const Value : String);

automated

procedure Clear;

procedure Ineart(Const Text : String);

procedure Save;

procedure Close;

procedure FileName : String read GretFileName write

SetFileName;

procedure Modified : WordBool read GretModified

end; 

        TMemeDoc类是MemoEdit程序的内部自动化类,因此不需要注册。外部OLE自动化控制器对它不能直接引用。如果要使外部控制器对自动化对象进行操作,则要在声明自动化对象的单元中调用Automation. RegisterClass 进行注册。例程MemoAuto 单元定义了TMemoApp对象并进行注册。 

  unit MemoAuto

  …

type

TMemoApp = Class(TAutoObject)

implementation



  procedure RegisterMemoApp

Const

AutoClassInfo : TAutoClassInfo = (

AutoClass : TMemoApp;

ProgID : MemoEdit,Application

ClassIn : '{FIFF4880 - 200D - 11CF - BDCF - D020AFOE5B81}';

Description : 'Memo Editor Application';

Instancing : acSingle Instance );

begin

Automation,RegisterClass(AutoClassInfo)

end;

inibialization

RegisterMemoApp;

end; 

        自动化对象要在initialization部分中对自动化对象进行注册。 注册的信息用以唯一辨识服务器对象。把一个自动化对象加入到服务器中要用到这些信息。程序一旦注册了自动化对象,全局自动化对象将用OLE自动化API进行自动管理。

  注册后的OLE自动化对象是引用记数的,因为对象可能被多个控制器控制。当使用完一个OLE对象,调用Release方法,Release可减少引用数目,当引用数目为零时,调用Free方法释放对象。

  通常把OLE对象作为变体类型(variants)进行输出,任何OLE 对象的方法和属性必须返回一个包含OLE对象的变体类型,TAutoObject提供了一个变体类型的OLEObject属性。控制器不能直接得到服务器中的类或指针,而是引用OLE对象的OLEObject属性。

  例程MemoAuto单元的NewMemo函数就是通过引用OLEObject 属性而提供引用TMemoDoc对象的接口。 

  function TMemoApp,NewMemo : Variant;

begin

Result := MainForm,CreateMemo(' '),OleObject;

end; 

8.4.2 创建OLE自动化服务器 

  OLE自动化服务器是应用程序或动态链接库(DLL),它可向OLE 自动化控制器输出OLE对象。 MemoEditdpr 就是OLE 自动化服务器, 在MemoAuto 单元中注册了MemoEdit.Appdication自动化类,所有OLE控制器均可对MemoEdit.Application进行引用。

  在Windows环境下有两种OLE自动化服务器,进程内服务器和进程外服务器, Delphi可创建这两种服务器。

  进程内服务器是输出OLE自动化对象的动态链接库。因为OLE自动化对象来自于DLL,

对象是控制器程序的同一窗体进程,进程内服务器适合于创建共享的程序模块, 而这个模块可以被用不同语言编写的多个程序所共享。 进程内服务器被调用时在同一地址中运行,这样就不需要控制器进行调度,以避免处理大量的消息句柄。 进程外服务器是能输出OLE自动化对象的应用程序。

  有些OLE自动化服务器只能创建和输出一个OLE对象,有些服务器则可以处理多个OLE对象,另外一些服务器不能输出OLE对象,只能在程序内部使用OLE对象。 服务器与其能输出的对象数目的关系称为实例(instancing)。

  在创建OLE 自动化对象时必须定义实例, 这样, 在创建一个OLE 自动化对象时,Windows就能决定是否创建一个新的服务器实例。表8.5列出三种实例类型。

表8.6 实例的取值及含义

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

instancing类型          含义

─────────────────────────────── ───────

internal OLE对象是应用程序的内部对象,对象不需要注册,外部进程不能创

          建此对象

Single 每个服务器实例只能输出一个OLE对象实例, 若控制器需要多个OLE

 

          对象实例,WIndows为第一个OLE对象创建一个服务器实例

Multiple 一个服务器能创建和输出多个OLE 对象实例, 进程内服务器大多是

         Multiple类型

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 

  每个使用OLEAuto单元的工程文件自动地拥有一个叫Automation的对象,它是非可视对象。就象Application部件拥有Delphi应用程序的一些信息一样,Automation对象也拥有服务器的一些信息,其中最重要的是StartMode属性和OnLastRelease事件。

  StartMode指示OLE自动化服务器打开方式打开的目的。表8.7列出StartMode四种取值。 

表8.7 StartMode 的取值及含义

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  取值          含义

───────────────────────────────

  SmStandAlone 用户启动应用程序

  SmAutomation Windows为创建OLE对象而启动程序

  SmRegSever 应用程序仅为注册一个或多个OLE对象而启动

  SmUnregSever 应用程序仅为注销一个或多个OLE对象而启动

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 

  当StartMode模式是SmAutomation,而用户不再需要服务器时发生OnLastRelease 事件。此时所有OLE控制器释放了由服务器创建的对象。缺省情况下,服务器关闭实例,但OnLastRelease 事件可根据实际情况是否关闭。OnLastRelease 事件可得到一个叫ShutDown的布尔型变量。把ShutDown设置成True,则在最后一个OLE对象释放时服务器不关闭。

  无论创建何种自动化服务器,必须定义对控制器的界面,包括定义和注册OLE对象,OLE自动化对象的属性和方法。定义界面主要是为了控制器能够引用它们。

  对已存在的自动化服务器界进行修改时,要确保向上兼容 ,不要删去已有的属性、方法,这样会导致已存在的自动化控制器发生错误,修改服务器只能增加属性和方法。

  创建OLE自动化服务器第一步是创建服务器自身。即创建能输出OLE 对象的应用程序或动态链接库。这主要取决于是创建进程内服务器还是进程外服务器。

  创建进程内服务器,即动态链接库:

  1.创建动态链接库;

  2.在工程文件的uses条款中加入OLEAuto单元;

  3.在DLL中输出四个标准入口,即加入以下代码。 

  exports

DLLGetClassObject,DLLCanUnloadNow;

DLLRegisterServer,DLLUnregisterServer; 

以上代码必须准确拼写,包括大小写。与Object Pascal的其它项目不同,这些代码

对大小写敏感。

  创建进程外服务器:

  1.创建一个Delphi应用程序;

  2.在工程文件的begin之后加入以下代码; 

  if Automation,Server Registration then Exit; 

创建服务器之后,应该向服务器加入OLE自动化对象,这个过程大部分是自动完成的,但必须向Delphi的自动化对象专家提供必要的信息。

  把OLE自动化对象加入服务器:

  1.在Delphi集成开发环境中选择File| New 菜单项, 并在对象集中选择Automation

Object,Delphi打开自动化对象专家。

2.给自动化对象命名

   这是服务器内部标识OLE对象的名字,必须是个有效的面象对象Pascal标识符,习惯上以T字母开头;

  3.给OLE类命名

   该名用以外部控制器创建对象。当服务器在Windows中注册OLE对象, 就以这个名字在系统注册。控制器使用这个名字调用CreateOLEObject来创建对象。

  4.描述要输出的对象。

  5.定义对象的实例(instancing),进程内服务器常定义为Multiple,进程外服务器常定义为Single;

  6.选择OK键完成该过程

   自动化对象专家将产生以下代码:

   ● 从TAutoObject派生下来的自动化对象定义,但没有定义任何属性方法;

   ● 调用DelphiOLE自动化管理器的注册代码,管理器负责Windows中注册服务器和对象。 

  在注册代码中包括一个自动产生的ID号,这个ID号是全局唯一的,通常不要修改。每个ID号与一个OLE类名相对应,如果其中之一被改变,应用程序在使用时会发生错误。

  在创建了服务器并把OLE自动化对象加入服务器之后,控制器程序就可以对服务器进行操纵。 

8.4.3 自动化另一程序 

  每个服务器在系统注册中有一个叫ProgID的关键定,主要用以控制器辨识服务器。任何控制器可以用ProgID号来创建OLE对象实例。例程AutoForm是控制器程序,它在其主窗体创建了OLE对象实例。 

  procedure TMainForm.FormCreate(Sender : TObject);

begin

try

MemoEdit := CreateOleObject('MemoEdit.Application');

except

MessageDlg(

'An instance of the "MemoEdit Application"OLE Automation Class could

not be created,Make sure that the MemoEdit application has been registered

using a "MemoEdit|regserver"command line',

mtError,[mbok],0)

Halt;

end;

end; 

        控制器创建了OLE自动化对象实例后,可对其进行操纵。OLE自动对象包括属性和方法,虽然OLE自动化对象与面向对象Pascal中的对象不是同一概念,但Delphi允许使用与类似的语法对OLE对象的方法进行调用。

  AutoForm的很多过程引用了OLE自动化对象的方法: 

  procedure TMainForm,TileButtonClick(Sender : Tobject);

begin

MemoEdit,TileWindow;

end; 

其中TileWindows是OLE对象TMemoApp中定义的方法。

  AutoForm还通过TMemoApp的NewMemo方法获得了对服务器内部OLE对象TMemoDoc 的引用。

 procedure TMainForm,CreateButtonClick(Sender : TObject);

var

I : Integer;

begin

CloseMemo

for I := 1 to 3 do Memos[2] := MemoEdit.NewMemo;

end; 

其中NewMemo在MemoAuto单元中定义如下: 

  function IMemoApp.NewMemo : Variant;

begin

Result := MainForm,CreateMemo(' '),OleObject;

end;

控制器在获得服务器的内部OLE对象后,可以引用其方法: 

  procedure TMainForm.AddTextButtonClick(Sender,TObject);

var

I : Integer;

begin

for I := 1 to 3 do

if not var IsEmpty(Memo[I]) then

Memo[I],Insert{'This text was added through OLE Automation'#13#10);

end;

Insert是TMemoDoc中定义的方法,用以在子窗体中插入字符串。


想死你们了!

TOP

DELPHI基础教程

第九章 Delphi拖放编程

  拖放(DragDrop)是Windows提供的一种快捷的操作方式。作为基于Windows的开发工具,Delphi同样支持拖放操作,而且开发应用系统的拖放功能十分方便,真正体现了Delphi的强大功能和方便性。

  Delphi提供的所有控件(Control,即能获得输入焦点的部件)都支持拖放操作,并有相应的拖放属性、拖放事件和拖放方法。下面我们先介绍控件的拖放支持,而后再给出开发拖放操作的一般步骤和应用实例。 

9.1 控件的拖放支持 

  拖放操作中控件可以分为源控件和目标控件两类。绝大部分控件既可以作为源控件也可以作为目标控件。但也有一部分控件只能支持其中的一种。 

9.1.1 拖放属性 

  拖放属性主要有两个:

  ● DragMode : 拖动模式

  ● DragCursor : 拖动光标 

  它们都是在拖放的源控件中设置。DragMode控制用户在运行时间内当在控件上按下鼠标时控件如何反应。如果DragMode置为dmAutomatic,那么当用户在控件上按下鼠标时拖动自动开始;如果DragMode置为dmManual(这是缺省值),则将通过处理鼠标事件来判断一个拖动是否可以开始。

  DragCursor用于选择拖动时显示的光标,缺省值是CrDrag,一般不要去修改它。在程序设计过程中通用的界面规范应该得到开发者的尊重。但有时候为了特定的目的,开发者也可以把自己设计的光标赋给DragCursor。 

9.1.2 拖放事件 

  拖放事件主要有三个:

  ●OnDragOver:拖动经过时激发

  ●OnDragDrop:拖动放下时激发

  ●OnEndDrop :拖动结束时激发 

  前两个事件由目标控件响应,后一个事件由源控件响应。

  OnDragOver事件最主要的功能是确定当用户就地放下拖动时控件是否可以接受。它的参数包括: 

Source : TObject;  {源控件}

X,Y : Integer; {光标位置}

State : TDragState; {拖动状态}

var Accept : Boolean {能否接受} 

  TDragState是一个枚举类型,表示拖放项目与目标控件的关系。 

   type

TDragState = (dsDragEnter, dsDragLeave, dsDragMove);

  不同取值的意义如下表:

表9.1 DragState 的取值与意义

━━━━━━━━━━━━━━━━━━━━━━━━━━━

  取 值 意 义

───────────────────────────

dsDragEnter 拖动对象进入一个允许拖动对象放下

的控件中。为缺省状态。

dsDragLeave 拖动对象离开一个允许拖动对象放下

的控件。

dsDragMove 拖动对象在一个允许拖动对象放下的

控件内移动。

━━━━━━━━━━━━━━━━━━━━━━━━━━━  

  用户可以利用提供的参数来确定放下的拖动是否可被接受,如:

  ● 判断源控件类型: 

   Accept := Source is TLabel;

  ● 判断源控件对象: 

   Accept := (Source = TabSet1);

  ● 判断光标位置:

见(9.2),(9.3)中的例程。 

● 判断拖动状态: 

   If (Source is TLabel) and (State = dsDragMove) then

   begin

source.DragIcon := ' New.Ico ';

Accept := True;

   end

   else

   Accept := False;

  当Accept=True时,目标控件可以响应OnDragDrop事件,用于确定拖动被放下后程序如何进行处理。

  OnDragDrop事件处理过程的参数包括源控件和光标位置。这些信息可用于处理方式的确定。

  OnEndDrag事件是在拖动操作结束后由源控件来进行响应的,用于源控件进行相应的处理。拖动操作结束既包括拖动放下被接受,也包括用户在一个不能接受放下的控件上释放了鼠标。该事件处理过程的参数包括目标控件(Target)和放下位置的坐标。如果Target=nil, 表示拖动项目没有被任何控件接受。

  在第3节将介绍的文件拖放移动、拖放拷贝操作中,如果操作成功,则文件列表框应更新显示内容。下面这段程序用于实现这一功能。 

procedure TFMForm.FileListEndDrag(Sender, Target: TObject; X, Y: Integer);

begin

if Target <> nil then FileList.Update;

end;

  除以上介绍的三个事件外,还有一个事件OnMouseDown 也常用于拖放操作的响应。OnMouseDown虽然不是一个专门的拖放事件,但在人工模式下拖动的开始是在这一事件的处理过程中实现的。 

9.1.3 拖放方法 

  拖放方法有三个:

  ●BeginDrag : 人工方式下开始一个拖动

  ●EndDrag : 结束一个拖动

  ●Dragging : 判断一个控件是否正被拖动 

  这三个方法都被源控件使用。

  当DragMode置为dmManual时,拖动必须调用控件的BeginDrag方法才能开始。BeginDrag有一个布尔参数Immediate。如果输入参数为True,拖动立即开始,光标改变到DragCursor的设置。如果输入参数为False,直到用户将光标移动了一定的距离(5个象素点)后才改变光标,开始拖动。这就允许控件接受一个OnClick事件而并不开始拖动操作。

  EndDrag方法中止一个对象的被拖动状态。它有一个布尔参数Drop。如果Drop设置为True,被拖动的对象在当前位置放下(能否被接受由目标控件决定);如果Drop设置为False,则拖动就地被取消。

  下面一段程序表明当拖动进入一控制面板时拖动被取消。     

procedure TForm1.Panel1DragOver(Sender, Source: TObject; X, Y: Integer;

State: TDragState; var Accept: Boolean);

begin

Accept := False;

if (Source is TLabel) and (State = dsDragEnter) then

(Source as TLabel).EndDrag(False);

end;

  Draging方法判断一个控件是否正被拖动。在下面的例子中当用户拖动不同的检查框时窗口改变为不同的颜色。 

procedure TForm1.FormActivate(Sender: TObject);

begin

CheckBox1.DragMode := dmAutomatic;

CheckBox2.DragMode := dmAutomatic;

CheckBox3.DragMode := dmAutomatic;

end; 

procedure TForm1.FormDragOver(Sender, Source: TObject; X, Y: Integer;

State: TDragState; var Accept: Boolean);

begin

if CheckBox1.Dragging then

Color := clAqua;

if CheckBox2.Dragging then

Color := clYellow;

if CheckBox3.Dragging then

Color := clLime;

end; 

9.2 开发拖放功能的一般步骤 

  拖放作为Windows提供的一种方便操作对象的功能,在Delphi中可以很容易地开发出来。根据拖放操作的过程可以把开发步骤划分为四个阶段,即:

  ● 开始拖动操作

  ● 接收拖动项目

  ● 放下拖动项目

  ● 终止拖动操作 

  在介绍过程中我们将结合一个TabSet(标签集)的拖放操作实例。界面设计如图。在运行时当用户把一个标签拖动到另一个标签的位置时,该标签将移动到该位置并引起标签集的重新布置。

9.2.1 开始拖动操作 

  当拖动模式(DragMode)设置为dmAutomatic时,用户在源控件上按下鼠标时拖动自动开始;当设置为dmManual时通过处理鼠标事件来决定拖动是否开始。如果想开始拖动调用BeginDrag方法。

  在TabSet拖放中,我们用下面的MouseDown事件处理过程来开始一个标签的拖动。首先判断按下的是否是左键,而后再判断项目是否合法。 

procedure TForm1.TabSet1MouseDown(Sender: TObject; Button: TMouseButton;

Shift: TShiftState; X, Y: Integer);

var

DragItem: Integer;

begin

if Button = mbLeft then

begin

DragItem := TabSet1.ItemAtPos(Point(X, Y));

if (DragItem > -1) and (DragItem < TabSet1.Tabs.Count) then

TabSet1.BeginDrag(False);

end;

end; 

9.2.2 接收拖动项目 

  一个控件能否接收拖动项目是由该控件的OnDragOver事件决定的。在TabSet拖动中,主要是利用鼠标的位置进行判断。  

procedure TForm1.TabSet1DragOver(Sender, Source: TObject; X, Y: Integer;

State: TDragState; var Accept: Boolean);

var

DropPos: Integer;

begin

if Source = TabSet1 then

begin

DropPos := TabSet1.ItemAtPos(Point(X, Y));

Accept := (DropPos > -1) and (DropPos <> TabSet1.TabIndex) and

(DropPos < TabSet1.Tabs.Count);

end;

else

Accept := False;

end; 

9.2.3 放下拖动项目 

  当OnDragOver事件处理过程返回的Accept为True且项目被放下时,由OnDragDrop事件处理过程来完成拖动放下后的响应。在TabSet拖放实例中是改变标签的位置。 

procedure TForm1.TabSet1DragDrop(Sender, Source: TObject; X, Y: Integer);

var

OldPos: Integer;

NewPos: Integer;

begin

if Source = TabSet1 then

begin

OldPos := TabSet1.TabIndex;

NewPos := TabSet1.ItemAtPos(Point(X, Y));

if (NewPos > -1) and (NewPos <> OldPos) then

TabSet1.Tabs.Move(OldPos, NewPos);

end;

end; 

9.2.4 结束拖动操作 

  结束拖动操作的方式有两种:或者是用户释放了鼠标键或者是程序用EndDrag方法强行中止拖动。结束拖动操作的后果有两种:放下被接受或放下被忽略。

  拖动操作结束后源控件都要收到一条消息响应拖动结束事件OnEndDrag。 

9.3  拖放应用实例:文件管理器的拖放支持 

  在第六章最后开发的文件管理器应用实例,虽然功能上已初具规模,但在操作上与Windows的文件管理器相比还有很大不足。其中最大的缺陷是它不支持文件的拖放移动和拖放拷贝。在这一章结束的时候,我们可以来弥补这一缺陷了。

  文件拖放移动指的是当用户把一个文件拖动到目录树下的某一目录并放下时,文件将自动移动到该目录中;文件拖放拷贝指的是当用户把一个文件拖动到某个驱动器标签上并放下时,文件将自动拷贝到该驱动器的当前目录下。作为源控件的文件列表框和作为目标控件的目录树、驱动器标签可以位于不同的子窗口。驱动器的当前目录是任一子窗口的最新操作结果,而不论这一子窗口与拖动源、拖动目标是否有关系。

  为了实现上述功能,有两个问题必须首先解决:

  1.如何记录每一驱动器的当前目录?

  为此我们定义了一个全局变量: 

  var

CurentDirList: Array[0...25] of string[70]; 

在DirectoryOutline的OnChange事件中: 

procedure TFMForm.DirectoryOutlineChange(Sender: TObject);

begin

CreateCaption;

FileList.clear;

FileList.Directory := DirectoryOutline.Directory;

FileList.Update;

CurrentDirList[DriveTabSet.TabIndex] := DirectoryOutline.Directory;

FileManager.DirectoryPanel.Caption := DirectoryOutline.Directory;

end;  

  由于DriveTabSet在响应OnDragDrop事件前先响应OnClick事件,并由该事件激发DirectoryOutline的Onchange事件,因而可保证在任何时候OnDragDrop事件中用到的CurrentDirList数组项不为空字符串。

 2.如何保证移动、拷贝与子窗口的无关性?

  在这里一个关键问题是我们判断源控件时是用is操作符进行类型检查: 

If Source is TFileList then



  如果我们用下面的语句: 

  If Source = FileList then

   …

  则移动、拷贝操作将限制在本子窗口范围内。

  当解决了上述问题后我们的工作就只是遵循拖放的一般开发步骤,按步就班来完成了。

  1.FileList开始拖动操作 

procedure TFMForm.FileListMouseDown(Sender: TObject; Button: TMouseButton;

Shift: TShiftState; X, Y: Integer);

begin

if Button = mbLeft then

with Sender as TFileListBox do

begin

if ItemAtPos(Point(X, Y), True) >= 0 then

BeginDrag(False);

end;

end;

  ItemAtPos用来检查当前是否有文件存在。而BeginDrag方法传递参数False, 允许FileList单独处理鼠标事件而并不开始拖动。事实上这种情况是大量存在的。 

  2.DirectoryOutline、DriveTabSet决定是否能接受拖动的就地放下。  

procedure TFMForm.DirectoryOutlineDragOver(Sender, Source: TObject; X,

Y: Integer; State: TDragState; var Accept: Boolean);

begin

if Source is TFileListBox then

Accept := True;

end; 

procedure TFMForm.DriveTabSetDragOver(Sender, Source: TObject; X,

Y: Integer; State: TDragState; var Accept: Boolean);

var

PropPos: Integer;

begin

if Source is TFileListBox then

with DriveTabSet do

begin

PropPos := ItemAtPos(Point(X,Y));

Accept := (PropPos > -1) and (PropPos < Tabs.Count);

end;

end;

  DirectoryOutline是无条件的接受,而DriveTabSet需检查是否是合法的标签。 

  3.拖动放下的响应

  DirectoryOutline的拖动放下用于实现文件移动功能。程序中调用ConfirmChange事件处理过程,目标路径由DirctoryOutline.Items[GetItem(X,Y)].FullPath来得到。  

procedure TFMForm.DirectoryOutlineDragDrop(Sender, Source: TObject; X,

Y: Integer);

begin

if Source is TFileListBox then

with DirectoryOutline do

begin

ConfirmChange('Move',FileList.FileName, Items[GetItem(X, Y)].FullPath);

end;

end;

  DriveTabSet的拖动放下用于实现文件拷贝功能。程序中把当前位置转化为相应的驱动器号,目标路径由CurrentDirList[DriveTabSet.TabIndex]获得。 

procedure TFMForm.DriveTabSetDragDrop(Sender, Source: TObject; X,Y: Integer);

var

APoint: TPoint;

begin

APoint.X := X; APoint.Y := Y;

DriveTabSet.TabIndex := DriveTabSet.ItemAtPos(APoint);

if Source is TFileListBox then

with DriveTabSet do

begin

if CurrentDirList[TabIndex] <> '' then

ConfirmChange('Copy',TheFilename,CurrentDirList[TabIndex]);

end;

end; 

4.FileList响应拖动结束,更新文件列表 

procedure TFMForm.FileListEndDrag(Sender, Target: TObject; X, Y: Integer);

begin

if Target <> nil then FileList.Update;

end; 

到目前为止,我们的文件管理器功能已足够强大。 不过还有许多问题值得读者去进

一步探讨,如:

  1.文件与应用程序关联的建立;

  2.在文件列表框中显示更多的文件信息;

  3.文件列表框中的文件按后缀各排序等。

  文件管理器是一个真正的综合例程,对它的钻研会使您更进一步模到Delphi编程的精髓。


想死你们了!

TOP

DELPHI基础教程

第十章 动态链接库编程(一)
10.1 Windows的动态链接库原理 

  动态链接库(DLLs)是从C语言函数库和Pascal库单元的概念发展而来的。所有的C语言标准库函数都存放在某一函数库中,同时用户也可以用LIB程序创建自己的函数库。在链接应用程序的过程中,链接器从库文件中拷贝程序调用的函数代码,并把这些函数代码添加到可执行文件中。这种方法同只把函数储存在已编译的.OBJ文件中相比更有利于代码的重用。

  但随着Windows这样的多任务环境的出现,函数库的方法显得过于累赘。如果为了完成屏幕输出、消息处理、内存管理、对话框等操作,每个程序都不得不拥有自己的函数,那么Windows程序将变得非常庞大。Windows的发展要求允许同时运行的几个程序共享一组函数的单一拷贝。动态链接库就是在这种情况下出现的。动态链接库不用重复编译或链接,一旦装入内存,Dlls函数可以被系统中的任何正在运行的应用程序软件所使用,而不必再将DLLs函数的另一拷贝装入内存。 

10.1.1 动态链接库的工作原理 

  “动态链接”这几字指明了DLLs是如何工作的。对于常规的函数库,链接器从中拷贝它需要的所有库函数,并把确切的函数地址传送给调用这些函数的程序。而对于DLLs,函数储存在一个独立的动态链接库文件中。在创建Windows程序时,链接过程并不把DLLs文件链接到程序上。直到程序运行并调用一个DLLs中的函数时,该程序才要求这个函数的地址。此时Windows才在DLLs中寻找被调用函数,并把它的地址传送给调用程序。采用这种方法,DLLs达到了复用代码的极限。

  动态链接库的另一个方便之处是对动态链接库中函数的修改可以自动传播到所有调用它的程序中,而不必对程序作任何改动或处理。

  DLLs不仅提供了函数重用的机制,而且提供了数据共享的机制。任何应用程序都可以共享由装入内存的DLLs管理的内存资源块。只包含共享数据的DLLs称为资源文件。如Windows的字体文件等。 

10.1.2 Windows系统的动态链接库 

  Windows本身就是由大量的动态链接库支持的。这包括Windows API函数 ( KRNLx86.EXE,USER.EXE,GDI.EXE,…),各种驱动程序文件,各种带有.Fon和.Fot 扩展名的字体资源文件等。Windows还提供了针对某一功能的专用DLLs,如进行DDE编程的ddeml.dll,进行程序安装的ver.dll等。

  虽然在编写Windows程序时必然要涉及到DLLs,但利用Delphi ,用户在大部分时候并不会注意到这一点。这一方面是因为Delphi提供了丰富的函数使用户不必直接去使用Windows API;另一方面即使使用Windows API,由于Delphi把API函数和其它Windows DLLs函数重新组织到了几个库单元中,因而也不必使用特殊的调用格式。所以本章的重点放在编写和调用用户自定义的DLLs上。

  使用传统的Windows编程方法来创建和使用一个DLLs是一件很令人头痛的事,正如传统的Windows编程方法本身就令人生畏一样。用户需要对定义文件、工程文件进行一系列的修改以适应创建和使用DLLs的需要。Delphi的出现,在这一方面,正如在其它许多方面所做的那样,减轻了开发者的负担。更令人兴奋的是Delphi利用DLLs 实现了窗体的重用机制。用户可以将自己设计好的窗体储存在一个DLLs中,在需要的时候可随时调用它。 

10.2 DLLs的编写和调用 

10.2.1 DLLs的编写 

  在Delphi环境中,编写一个DLLs同编写一个一般的应用程序并没有太大的区别。事实上作为DLLs 主体的DLL函数的编写,除了在内存、资源的管理上有所不同外,并不需要其它特别的手段。真正的区别在工程文件上。

  在绝大多数情况下,用户几乎意识不到工程文件的存在,因为它一般不显示在屏幕上。如果想查看工程文件,则可以打开View菜单选择Project Source项,此时工程文件的代码就会出现在屏幕的Code Editor(代码编辑器)中。

  一般工程文件的格式为: 

  program   工程标题;

  uses     子句;

  程序体 

  而DLLs工程文件的格式为: 

  library 工程标题;

  uses 子句;

  exprots 子句;

  程序体 

  它们主要的区别有两点:

  1.一般工程文件的头标用program关键字,而DLLs工程文件头标用library 关键字。不同的关键字通知编译器生成不同的可执行文件。用program关键字生成的是.exe文件,而用library关键字生成的是.dll文件;

  2.假如DLLs要输出供其它应用程序使用的函数或过程,则必须将这些函数或过程列在exports子句中。而这些函数或过程本身必须用export编译指令进行编译。

  根据DLLs完成的功能,我们把DLLs分为如下的三类:

1.完成一般功能的DLLs;

2.用于数据交换的DLLs;

3.用于窗体重用的DLLs。

  这一节我们只讨论完成一般功能的DLLs,其它内容将在后边的两节中讨论。 

10.2.1.1 编写一般DLLs的步骤 

  编写一般DLLs的步骤如下:

  1.利用Delphi的应用程序模板,建立一个DLLs程序框架。

  对于Delphi 1.0的用户,由于没有DLLs模板,因此:

  (1).建立一个一般的应用程序,并打开工程文件;

  (2).移去窗体和相应的代码单元;

  (3).在工程文件中,把program改成library,移去Uses子句中的Forms,并添加适当的库单元(一般SysUtils、Classes是需要的),删去begin...end之间的所有代码。

  2.以适当的文件名保持文件,此时library后跟的库名自动修改;

  3.输入过程、函数代码。如果过程、函数准备供其它应用程序调用,则在过程、函数头后加上export 编译指示;

  4.建立exports子句,包含供其它应用程序调用的函数和过程名。可以利用标准指示 name 、Index、resident以方便和加速过程/函数的调用;

  5.输入库初始化代码。这一步是可选的;

  6.编译程序,生成动态链接库文件。 

10.2.1.2 动态链接库中的标准指示 

  在动态链接库的输出部分,用到了三个标准指示:name、Index、resident。

  1.name

  name后面接一个字符串常量,作为该过程或函数的输出名。如: 

exports

InStr name MyInstr;

  其它应用程序将用新名字(MyInstr)调用该过程或函数。如果仍利用原来的名字(InStr),则在程序执行到引用点时会引发一个系统错误。

  2.Index

  Index指示为过程或函数分配一个顺序号。如果不使用Index指示,则由编译器按顺序进行分配。

  Index后所接数字的范围为1...32767。使用Index可以加速调用过程。

  3.resident

  使用resident,则当DLLs装入时特定的输出信息始终保持在内存中。这样当其它应用程序调用该过程时,可以比利用名字扫描DLL入口降低时间开销。

  对于那些其它应用程序常常要调用的过程或函数,使用resident指示是合适的。例如: 

exports

InStr name MyInStr resident; 

10.2.1.3 DLLs中的变量和段 

一个DLLs拥有自己的数据段(DS),因而它声明的任何变量都为自己所私有。调用它的模块不能直接使用它定义的变量。要使用必须通过过程或函数界面才能完成。而对DLLs来说,它永远都没有机会使用调用它的模块中声明的变量。

  一个DLLs没有自己的堆栈段(SS),它使用调用它的应用程序的堆栈。因此在DLL中的过程、函数绝对不要假定DS = SS。一些语言在小模式编译下有这种假设,但使用Delphi可以避免这种情况。Delphi绝不会产生假定DS = SS的代码,Delphi的任何运行时间库过程/函数也都不作这种假定。需注意的是如果读者想嵌入汇编语言代码,绝不要使SS和DS登录同一个值。 

10.2.1.4 DLLs中的运行时间错和处理 

  由于DLLs无法控制应用程序的运行,导致很难进行异常处理,因此编写DLLs时要十分小心,以确保被调用时能正常执行 。当DLLs中发生一个运行时间错时,相应DLLs并不一定从内存中移去(因为此时其它应用程序可能正在用它),而调用DLLs的程序异常中止。这样造成的问题是当DLLs已被修改,重新进行调用时,内存中保留的仍然可能是以前的版本,修改后的程序并没有得到验证。对于这个问题,有以下两种解决方法:

  1.在程序的异常处理部分显式将DLL卸出内存;

  2.完全退出Windows,而后重新启动,运行相应的程序。

  同一般的应用程序相比,DLL中运行时间错的处理是很困难的,而造成的后果也更为严重。因此要求程序设计者在编写代码时要有充分、周到的考虑。 

10.2.1.5 库初始化代码的编写 

  传统Windows中动态链接库的编写,需要两个标准函数:LibMain和WEP,用于启动和关闭DLL。在LibMain中,可以执行开锁DLL数据段、分配内存、初始化变量等初始化工作;而WEP在从内存中移去DLLs前被调用,一般用于进行必要的清理工作,如释放内存等。Delphi用自己特有的方式实现了这两个标准函数的功能。这就是在工程文件中的begin...end部分添加初始化代码。和传统Windows编程方法相比,它的主要特色是:

  1.初始化代码是可选的。一些必要的工作(如开锁数据段)可以由系统自动完成。所以大部分情况下用户不会涉及到;

  2.可以设置多个退出过程,退出时按顺序依次被调用;

  3.LibMain和WEP对用户透明,由系统自动调用。

  初始化代码完成的主要工作是:

  1.初始化变量、分配全局内存块、登录窗口对象等初始化工作。在(10.3.2)节“利用DLLs实现应用程序间的数据传输”中,用于数据共享的全局内存块就是在初始化代码中分配的。

  2.设置DLLs退出时的执行过程。Delphi有一个预定义变量ExitProc用于指向退出过程的地址。用户可以把自己的过程名赋给ExitProc。系统自动调用WEP函数,把ExitProc指向的地址依次赋给WEP执行,直到ExitProc为nil。

  下边的一段程序包含一个退出过程和一段初始化代码,用来说明如何正确设置退出过程。 

library Test;

{$S-}

uses WinTypes, WinProcs;

var

SaveExit: Pointer; 

procedure LibExit; far;

begin

if ExitCode = wep_System_Exit then

begin

{ 系统关闭时的相应处理 }

end

else

begin

{ DLL卸出时的相应处理 }

end;

ExitProc := SaveExit; { 恢复原来的退出过程指针 }

end; 

begin

{DLL的初始化工作 }

SaveExit := ExitProc; { 保存原来的退出过程指针 }

ExitProc := @LibExit; { 安装新的退出过程 }

end.

  在初始化代码中,首先把原来的退出过程指针保存到一个变量中,而后再把新的退出过程地址赋给ExitProc。而在自定义退出过程LibExit结束时再把ExitProc的值恢复。由于ExitProc是一个系统全局变量,所以在结束时恢复原来的退出过程是必要的。

  退出过程LibExit中使用了一个系统定义变量ExitCode,用于标志退出时的状态。 ExitCode的取值与意义如下: 

表10.1 ExitCode的取值与意义

━━━━━━━━━━━━━━━━━━━━━

取 值 意 义

—————————————————————

  WEP_System_Exit Windows关闭 

WEP_Free_DLLx DLLs被卸出

━━━━━━━━━━━━━━━━━━━━━ 

  退出过程编译时必须关闭stack_checking,因而需设置编译指示 {$S-} 。 

10.2.1.6 编写一般DLLs的应用举例 

  在下面的程序中我们把一个字符串操作的函数储存到一个DLLs中,以便需要的时候调用它。应该注意的一点是:为了保证这个函数可以被其它语言编写的程序所调用,作为参数传递的字符串应该是无结束符的字符数组类型(即PChar类型),而不是Object Pascal的带结束符的Srting类型。程序清单如下:

library Example;

uses

SysUtils,

Classes;

{返回字符在字符串中的位置}

function InStr(SourceStr: PChar;Ch: Char): Integer; export;

var

Len,i: Integer;

begin

Len := strlen(SourceStr);

for i := 0 to Len-1 do

if SourceStr = ch then

begin

Result := i;

Exit;

end;

Result := -1;

end;

exports

Instr Index 1 name 'MyInStr' resident;

begin

end. 

10.2.2 调用DLLs

  有两种方法可用于调用一个储存在DLLs中的过程。

  1.静态调用或显示装载

  使用一个外部声明子句,使DLLs在应用程序开始执行前即被装入。例如: 

  function Instr(SourceStr : PChar;Check : Char); Integer; far; external 'UseStr';

  使用这种方法,程序无法在运行时间里决定DLLs的调用。假如一个特定的DLLs在运行时无法使用,则应用程序将无法执行。

  2.动态调用或隐式装载

  使用Windows API函数LoadLibray和GetProcAddress可以实现在运行时间里动态装载DLLs并调用其中的过程。

  若程序只在其中的一部分调用DLLs的过程,或者程序使用哪个DLLs, 调用其中的哪个过程需要根据程序运行的实际状态来判断,那么使用动态调用就是一个很好的选择。

  使用动态调用,即使装载一个DLLs失败了,程序仍能继续运行。 

10.2.3 静态调用

  在静态调用一个DLLs中的过程或函数时,external指示增加到过程或函数的声明语句中。被调用的过程或函数必须采用远调用模式。这可以使用far过程指示或一个{$F +}编译指示。

  Delphi全部支持传统Windows动态链接库编程中的三种调用方式,它们是:

  ● 通过过程/函数名

  ● 通过过程/函数的别名

  ● 通过过程/函数的顺序号 

  通过过程或函数的别名调用,给用户编程提供了灵活性,而通过顺序号(Index)调用可以提高相应DLL的装载速度。 

10.2.4 动态调用 

10.2.4.1 动态调用中的API函数 

  动态调用中使用的Windows API函数主要有三个,即:Loadlibrary,GetProcAddress和Freelibrary。

   1.Loadlibrary: 把指定库模块装入内存

  语法为: 

  function Loadlibrary(LibFileName: PChar): THandle; 

LibFileName指定了要装载DLLs的文件名,如果LibFileName没有包含一个路径,则Windows按下述顺序进行查找:

  (1)当前目录;

  (2)Windows目录(包含win.com的目录)。函数GetWindowDirectory返回这一目录的路径;

  (3)Windows系统目录(包含系统文件如gdi.exe的目录)。函数GetSystemDirectory返回这一目录的路径;

  (4)包含当前任务可执行文件的目录。利用函数GetModuleFileName可以返回这一目录的路径;

  (5)列在PATH环境变量中的目录;

  (6)网络的映象目录列表。

  如果函数执行成功,则返回装载库模块的实例句柄。否则,返回一个小于HINSTANCE_ERROR的错误代码。错误代码的意义如下表: 

   表10.2 Loadlibrary返回错误代码的意义

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

错误代码         意        义

——————————————————————————————————————

    0 系统内存不够,可执行文件被破坏或调用非法

    2 文件没有被发现

    3 路径没有被发现

    5 企图动态链接一个任务或者有一个共享或网络保护错

    6 库需要为每个任务建立分离的数据段

     8 没有足够的内存启动应用程序

   10 Windows版本不正确

    11 可执行文件非法。或者不是Windows应用程序,或者在.EXE映

      像中有错误

    12 应用程序为一个不同的操作系统设计(如OS/2程序)

13 应用程序为MS DOS4.0设计

    14 可执行文件的类型不知道

    15 试图装载一个实模式应用程序(为早期Windows版本设计)

16 试图装载包含可写的多个数据段的可执行文件的第二个实例

    19 试图装载一个压缩的可执行文件。文件必须被解压后才能被装裁

    20 动态链接库文件非法

    21 应用程序需要32位扩展

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  假如在应用程序用Loadlibrary调用某一模块前,其它应用程序已把该模块装入内存,则Loadlibrary并不会装载该模块的另一实例,而是使该模块的“引用计数”加1。 

  2.GetProcAddress:捡取给定模块中函数的地址

  语法为: 

  function GetProcAddress(Module: THandle; ProcName: PChar): TFarProc; 

Module包含被调用的函数库模块的句柄,这个值由Loadlibrary返回。如果把Module设置为nil,则表示要引用当前模块。

  ProcName是指向含有函数名的以nil结尾的字符串的指针,或者也可以是函数的次序值。如果ProcName参数是次序值,则如果该次序值的函数在模块中并不存在时,GetProcAddress仍返回一个非nil的值。这将引起混乱。因此大部分情况下用函数名是一种更好的选择。如果用函数名,则函数名的拼写必须与动态链接库文件EXPORTS节中的对应拼写相一致。

  如果GetProcAddress执行成功,则返回模块中函数入口处的地址,否则返回nil。

3.Freelibrary:从内存中移出库模块

  语法为: 

  procedure Freelibrary(Module : THandle); 

Module为库模块的句柄。这个值由Loadlibrary返回。

  由于库模块在内存中只装载一次,因而调用Freelibrary首先使库模块的引用计数减一。如果引用计数减为0,则卸出该模块。

  每调用一次Loadlibrary就应调用一次FreeLibray,以保证不会有多余的库模块在应用程序结束后仍留在内存中。 

10.2.4.2 动态调用举例 

  对于动态调用,我们举了如下的一个简单例子。系统一共包含两个编辑框。在第一个编辑框中输入一个字符串,而后在第二个编辑框中输入字符。如果该字符包含在第一个编辑框的字符串中,则标签框显示信息:“位于第n位。”,否则显示信息:“不包含这个字符。”。如图是程序的运行界面。

输入检查功能的实现在Edit2的OnKeyPress事件处理过程中,程序清单如下。 

procedure TForm1.Edit2KeyPress(Sender: TObject; var Key: Char);

var

order: Integer;

txt: PChar;

PFunc: TFarProc;

Moudle: THandle;

begin

Moudle := Loadlibrary('c:\dlls\example.dll');

if Moudle > 32 then

begin

Edit2.text := '';

Pfunc := GetProcAddress(Moudle,'Instr');

txt := StrAlloc(80);

txt := StrPCopy(txt,Edit1.text);

Order := TInstr(PFunc)(txt,Key);

if Order = -1 then

Label1.Caption := '不包含这个字符 '

else

Label1.Caption := '位于第'+IntToStr(Order+1)+'位';

end;

Freelibrary(Moudle);

end;

  在利用GetProcAddess返回的函数指针时,必须进行强制类型转换: 

Order := TInstr(PFunc)(text,Key);

  TInStr是一个定义好了的函数类型: 

type

TInStr = function(Source: PChar;Check: Char): Integer; 

10.3 利用DLLs实现数据传输 

10.3.1 DLLs中的全局内存 

  Windows规定:DLLs并不拥有它打开的任何文件或它分配的任何全局内存块。这些对象由直接或间接调用DLLs的应用程序拥有。这样,当应用程序中止时,它拥有的打开的文件自动关闭,它拥有的全局内存块自动释放。这就意味着保存在DLLs全局变量中的文件和全局内存块变量在DLLs没有被通知的情况下就变为非法。这将给其它使用该DLLs的应用程序造成困难。

  为了避免出现这种情况,文件和全局内存块句柄不应作为DLLs的全局变量,而是作为DLLs中过程或函数的参数传递给DLLs使用。调用DLLs的应用程序应该负责对它们的维护。

  但在特定情况下,DLLs也可以拥有自己的全局内存块。这些内存块必须用gmem_DDEShare属性进行分配。这样的内存块直到被DLLs显示释放或DLLs退出时都保持有效。

  由DLLs管理的全局内存块是应用程序间进行数据传输的又一途径,下面我们将专门讨论这一问题。 

10.3.2 利用DLLs实现应用程序间的数据传输 

  利用DLLs实现应用程序间的数据传输的步骤为:

  1. 编写一个DLLs程序,其中拥有一个用gmem_DDEShare属性分配的全局内存块;

  2. 服务器程序调用DLLs,向全局内存块写入数据;

  3. 客户程序调用DLLs,从全局内存块读取数据。 

10.3.2.1 用于实现数据传输的DLLs的编写 

  用于实现数据传输的DLLs与一般DLLs的编写基本相同,其中特别的地方是:

  1. 定义一个全局变量句柄: 

var

hMem: THandle;

  2. 定义一个过程,返回该全局变量的句柄。该过程要包含在exports子句中。如: 

function GetGlobalMem: THandle; export;

begin

Result := hMem;

end;

  3. 在初始化代码中分配全局内存块:

程序清单如下: 

begin

hMem := GlobalAlloc(gmem_MOVEABLE and gmem_DDEShare,num);

if hMem = 0 then

MessageDlg('Could not allocate memory',mtWarning,[mbOK],0);

end.

  num是一个预定义的常数。

Windows API函数GlobalAlloc用于从全局内存堆中分配一块内存,并返回该内存块的句柄。该函数包括两个参数,第一个参数用于设置内存块的分配标志。可以使用的分配标志如下表所示。

表10.3 全局内存块的分配标志

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

标 志 意 义

—————————————————————————————————

gmem_DDEShare 分配可由应用程序共享的内存

gmem_Discardable 分配可抛弃的内存(只与gmem_Moveable连用)

gmem_Fixed 分配固定内存

gmem_Moveable 分配可移动的内存

gmem_Nocompact 该全局堆中的内存不能被压缩或抛弃

gmem_Nodiscard 该全局堆中的内存不能被抛弃

gmem_NOT_Banked 分配不能被分段的内存

gmem_Notify 通知功能。当该内存被抛弃时调用GlobalNotify函数

gmem_Zeroinit 将所分配内存块的内容初始化为零

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 

  有两个预定义的常用组合是:

GHND = gmem_Moveable and gmem_Zeroinit

GPTK = gmem_Fixed and gmem_Zeroinit

  第二个参数用于设置欲分配的字节数。分配的字节数必须是32的倍数,因而实际分配的字节数可能比所设置的要大。

  由于用gmem_DDEShare分配的内存在分配内存的模块终止时自动抛弃,因而不必调用GlobalFree显式释放内存。


想死你们了!

TOP

DELPHI基础教程

第十章 动态链接库编程(二)
10.3.2.2 服务器程序的编写 

  服务器程序必须包含对DLL的调用代码,如: 

function GetGlobalMem: THandle; far; external 'c:\dlls\glbmem';

  通过调用该函数,服务器可以获得全局内存块的句柄。

  在写入数据前,服务器必须锁定全局内存,以避免在写入过程中Windows移动该内存块的位置。

  函数GlobalLock锁定全局内存并返回指向该内存块的指针: 

pMem := GlobalLock(hMem);

  对pMem的任何修改都会反映到全局内存块中。

  对内存块进行操作后,调用GlobalUnLock进行解锁。内存块操作之后尽早解锁,有利于Windows充分利用内存资源。

  服务器写入数据的实现代码如下。 

var

hMem: THandle;

pMem: PChar;

begin

hMem := GetGlobalMem; {获得全局内存块的句柄}

if hMem <> 0 then

begin

pMem := GlobalLock(hMem); {加锁全局内存块}

if pMem <> nil then

begin

StrPCopy(pMem,Memo1.text); {向全局内存块写入数据}

GlobalUnlock(hMem); {解锁全局内存块}

end

else

MessageDlg('Couldnot Lock memory block',mtWarning,[mbOK],0);

end; 

10.3.2.3 客户程序的编写 

  客户程序几乎是服务器程序的翻版。唯一的区别在于一个是写入数据,一个是下载数据。

下面是客户从全局内存块下载数据的程序清单。 

var

hMem: THandle;

pMem: PChar;

begin

hMem := GetGlobalMem; {获得全局内存块的句柄}

if hMem <> 0 then

begin

pMem := GlobalLock(hMem); {加锁全局内存块}

if pMem <> nil then

begin

Memo1.text := StrPas(pMem); {从全局内存块读取数据}

GlobalUnlock(hMem); {解锁全局内存块}

end

else

MessageDlg('Couldnot Lock memory block',mtWarning,[mbOK],0);

end;

10.4 利用DLLs实现窗体重用 

  实现窗体重用是Delphi DLLs功能中一个引人注目的特色。当你创建了一个令自己满意的通用窗体并希望能在不同应用程序中使用,特别是希望能在非Delphi 应用程序中使用时,把窗体做进一个动态链接库中是最适当的。这样即使用其它工具开发的应用程序,如C++、Visual Basic等,也都可以去调用它。

  包含窗体的DLLs有100K左右的部件库(Component Library)开销。可以通过把几个窗体编译成一个DLLs来最小化这笔开销。DLl中的不同窗体可以共享部件库。 

10.4.1 利用DLLs实现窗体重用的一般步骤 

  利用DLLs实现窗体重用的步骤是:

  1.在集成开发环境(IDE)中,按自己的需要设计一个窗体;

  2.编写一个用于输出的函数或过程。在该函数或过程中,设计的窗体被实例化;

  3.重复步骤1、2,直到完成所有重用窗体的设计;

  4.打开工程文件,进行修改,以适应生成 .dll文件的需要:

  (1).把保留字program设为library;

  (2).从uses子句中去掉Forms单元;

  (3).移去begin,end之间的所有代码;

  (4).在uses子句下,begin…end块之前,添加保留字exprots。exports 后是输出函数名或过程名。

  5.编译生成DLLs文件;

  6.在其它应用程序中调用重用窗体。

  重用窗体的调用同一般DLLs函数或过程的调用完全一致,不再赘述。读者可参看下面的例子。 

10.4.2 窗体重用实例 

  下面我们通过一个具体的实例来说明窗体重用的设计过程。我们在一个名为passform.dll 的文件中储存了一个口令设置窗口和一个口令检查窗口。而后在一个Delphi 编写的程序和一个VB编写的程序中进行调用。事实证明这种方法是完全可行的。

10.4.2.1 窗体重用DLLs的设计 

  窗体重用DLLs的设计依照(10.4.1)中介绍的步骤进行。DLLs中的两个窗体 SetPassWordForm和GetPassWordForm分别用于设置和检查口令。它们的设计界面如图所示。

窗体类TSetPassWordForm定义了两个数据成员Verified和PassWord,用于记录口令确认状态和设置的口令。TSetPassWordForm的定义如下:

type

TSetPassWordForm = class(TForm)

Label1: TLabel;

Edit1: TEdit;

OKBtn: TBitBtn;

CancelBtn: TBitBtn;

procedure FormCreate(Sender: TObject);

procedure Edit1KeyPress(Sender: TObject; var Key: Char);

private

{ Private declarations }

Verified: Boolean;

public

{ Public declarations }

PassWord: PChar;

end;

  窗口生成时,对数据成员和部件状态进行初始化: 

procedure TSetPassWordForm.FormCreate(Sender: TObject);

begin

Verified := False;

PassWord := StrAlloc(40);

OKBtn.Enabled := False;

Label1.Caption := 'Please Input PassWord:';

end;

  按钮OKBtn在程序启动时Enabled属性设置为False,直到口令被正确设置后Enabled属性才恢复为True。这样就保证了只有口令被正确设置后,口令设置窗口才能正常关闭。否则只能按Cancel按钮取消。

  在口令设置代码单元中定义了一个输出函数SetPassWord,用于生成口令设置窗口并返回设置的口令: 

function SetPassWord(PWord: PChar): Boolean;

var

SetPassWordForm: TSetPassWordForm;

begin

Result := False;

SetPassWordForm := TSetPassWordForm.Create(Application);

try

with SetPasswordForm do

if ShowModal = mrOK then

begin

StrCopy(PWord,StrUpper(Password));

Result := True;

end;

finally

SetPasswordForm.Free;

end;

end;

  口令成功设置,把PassWord的值拷贝给PWord输出,并返回True。应该注意的是由于 PWord本身就是指针类型,指向一个字符串的地址,因而虽然PWord用于输出,但在参数表中仍为传值参数,而不是传址参数。另外调用函数StrCopy,要求PWord在传入前已分配内存,否则会导致一个一般保护错。try...finally用于保护窗口所占用内存资源在任何情况下都能正常释放,读者可参看第十二章。

  在口令设置窗口中,为了确保用户记住了设置的口令,在用户输入并按回车键后,要求用户再次输入进行确认。只有用户重新输入的字符串与原设置口令相同,口令设置窗口才能正常关闭 。否则将原设置口令清空,要求用户再次输入。以上功能的实现在编辑框的OnKeyPress事件处理过程中。 

procedure TSetPassWordForm.Edit1KeyPress(Sender: TObject; var Key: Char);

begin

if Edit1.text = '' then Exit;

if Key = #13 then

begin

if Verified then

if StrPas(PassWord) = Edit1.text then

begin

OKBtn.Enabled := True;

Edit1.Enabled := False;

OKBtn.SetFocus;

end

else

begin

Verified := False;

MessageDlg('PassWord is InValid.',mtWarning,[mbOK],0);

Edit1.text := '';

PassWord := '';

Label1.Caption := 'Please Input PassWord:';

end

else

begin

Verified := True;

StrPCopy(PassWord,Edit1.text);

Edit1.text := '';

Label1.caption := 'Please Verify PassWord:';

end;

Key := #0;

end;

end;

  口令检查窗口的实现相对简单,只定义了一个输出函数GetPassWord,用于生成口令检查窗口并返回口令检查的结果。 

function GetPassword(Password: PChar): Boolean;

var

GetPasswordForm: TGetPasswordForm;

begin

Result := False;

GetPasswordForm := TGetPasswordForm.Create(Application);

try

with GetPasswordForm do

if ShowModal = mrOK then

if UpperCase(Edit1.Text) <> StrPas(StrUpper(Password)) then

MessageDlg('Invalid Password', mtWarning, [mbOK], 0)

else

Result := True;

finally

PasswordForm.Free;

end;

end;

  PassWord为输入的参数,不能为空,由调用以上函数的程序负责维护。

  窗口中用户输入口令时回显在屏幕上的字符由编辑框的PassWordChar属性确定。

  在DLLs的工程文件中,把两个输出函数写到exports子句中。 

library PassForm; 

uses

GetPass in 'GETPASS.PAS' {PasswordForm},

Setpass in 'SETPASS.PAS' {SetPassWordForm}; 

exports

GetPassword,SetPassWord; 

begin

end. 

10.4.2.2 Delphi应用程序调用重用窗体 

  在Delphi应用程序中调用重用窗体,首先必须包含passform.dll的两个输出函数: 

function GetPassword(Password: PChar): Boolean;

far; external 'c:\dlls\PassForm';

function SetPassword(PassWord: PChar): Boolean;

far; external 'c:\dlls\PassForm';

  这位于程序单元的implementation部分。

口令设置部分的实现代码为: 

procedure TForm1.SetButtonClick(Sender: TObject);

begin

PassWord := StrAlloc(40);

if SetPassWord(PassWord) = False then

MessageDlg('PassWord is not set',mtInformation,[mbOK],0);

end;

  首先为口令字符串分配内存。当口令设置窗体按Cancel按钮取消时,显示相应的信息。

  口令检查部分的实现代码为: 

procedure TForm1.TestButtonClick(Sender: TObject);

begin

if PassWord = nil then

begin

MessageDlg('Set password first', mtInformation, [mbOK], 0);

SetButton.SetFocus;

Exit;

end;

if GetPassword(PassWord) then

Label1.Caption := 'You are Wellcome !'

else

Label1.Caption := 'Sorry,You are InValid User.';

end;

  根据口令检查的结果,在标签框中显示相应的信息。 

10.4.2.3 VB应用程序调用重用窗体 

  VB是微软公司极力推荐的一个可视化开发工具。它虽然并不支持动态链接库的创建,但可以调用标准的Windows API动态链接库和用其它语言编写的动态链接库。为了验证所生成DLLs的普适性,我们用VB开发了一个简单的程序来调用passform.dll中储存的窗体。

下面是VB程序的完整代码,和Delphi程序的对应部分基本一致。 

Option Explicit

Declare Function GetPassWord Lib "c:\dlls\passform.dll" (ByVal PassWord As String) As Integer

Declare Function SetPassWord Lib "c:\dlls\passform.dll" (ByVal PassWord As String) As Integer 

Dim PassWord As String * 40

Sub Check_Click ()

If PassWord = "" Then

MsgBox ("Enter sample password first")

SetPass.SetFocus

Else

If GetPassWord(PassWord) Then

StatusLbl.Caption = "You are Welcome!"

Else

StatusLbl.Caption = "Sorry,You are Invalid User."

End If

End If

End Sub

Sub SetPass_Click ()

If SetPassWord(PassWord) = 0 Then

MsgBox ("PassWord is not Set.")

End If

End Sub

  有关VB编程的一些具体问题,读者可参看有关的VB参考书。 

10.4.3 小结 

  本章我们讨论的是动态链接库编程。许多可视化开发工具(如Visual Basic)不支持 DLLs的创建,而Delphi在这里又有上乘的表现。特别是窗体重用机制是Delphi对Windows下DLLs编程的一个重大改进。在一般的DLLs编程中也体现了Delphi快捷、方便的特点。动态链接库是 Windows下程序组织的一种重要方式,使用动态链接库可以极大地保护用户在不同开发工具、不同时期所做的工作。利用动态链接库,用户可以逐步去构筑自己的程序模块库,为今后的工作积累素材。


想死你们了!

TOP

DELPHI基础教程

第十一章 Delphi应用程序的应用(一)
11.1 Help文件的建立 

  Help文件是Micosoft Windows3.0以上的版本提供的超文本帮助文件。利用这种超文本,用户可非常方便地使用帮助文件系统。帮助文件是以主题为主线进行编写的,一个主题可以跳转至相关的主题,也可按关键字进行主题查询。帮助文件与软件开发工具相结合,可实现应用程序的'上下文敏感',而且帮助系统自动装入。“上下文敏感”是指根据程序当前执行代码来显示Help文件的相应部分。

  Windows提供的很多应用程序都有帮助系统,读者可以从这些系统中了解应用程序的许多信息。

11.1.1 建立Help文件所需的工具和文件 

  程序员可为自己的应用程序建立帮助文件系统。但建立最基本的帮助系统, 必须有以下文件

  1. WinHelp 应用程序 ( WinHelp.exe) 。运行帮助系统实际上是运行用帮助源文件的

WindHelp程序。帮助文件只有通过WinHelp文件才能运行。

  2. 能创建主题的字处理器。这种处理器能以RTF格式保存文件, 能创建$,#,K,+脚标。RTF(Rich Text Format)格式是一个能记录各种文本特征的文件格式。这些特征包括字体大小、线型风格等。Microsoft Word 6.0处理器能满足以上要求。

  3. 一个能以ASCII格式保存文件的字处理器或编辑器,这是为了创建Help工程文件(.HPJ文件)。

  4. 帮助文件编译器(HCP.EXE或HC31.EXE),两种编译器均能编译在Windows3.1 环境中使用的帮助文件,但不能编译Windows3.0环境下的帮助文件。HCP.EXE是保护模式的编译器,能更好地使用内存空间。要在Windows的Dos窗口中使用HCP.EXE编译器。

  5. 帮助编译器所需的错误信息源文件(HCP.ERR或HC31.ERR)。如果帮助文件在编译过程中出现错误,WinHelp运行时将提示有关的错误信息,而这些信息保存在HCP.ERR或HC31.ERR文件中。

  以下工具能实现帮助系统的高级特征:

  1. 热点(Hotspot)编辑器(SHED.EXE);

  热点编辑器能创建分段超图像文件(.SHG)。这种文件包括一些分成多个热点的图像,当用户单击图像,将弹出一个窗口或跳转至另一主题。

  2. 多分辨率位图编译器(MRBE.EXE);

  这种编译器能将具备多种分辨率的位图结合到一个文件中,以供WinHelp 使用。WinHelp检查显示器的分辨率, 然后以相应的分辨率加以显示。

  3. 图像编辑器,它能以位图形式保存图像文件。 使用图像编辑器创建说明和自定义按钮。

  4. 绘图软件。用以创建除了位图之外的元文件(WMF);

  程序员可以直接把图像插入文本中,也可以用Windows剪贴板把图像粘贴至文本中。 

11.1.2 Help文件的创建 

  下面介绍最简单、最直接的创建Help文件的方法,假设在Word中创建主题。

  创建Help文件分以下4个步骤:

  1. 建立组成帮助文件的主题,并以RTF格式保存;

  2. 建立内容主题(Content Topic),并以RTF格式保存;

  3. 建立帮助工程文件(.HPJ)以文本格式保存;

  4. 将工程文件编译成帮助源文件(.HLP)。 

11.1.2.1 建立主题 

  一个简单的帮助主题包括主题题目(Title),主题文本(Text),脚标,主题内容,全局查询、打印。主题最好是带有题目,题目写在主题的第一行。用不同的字体大小、颜色以示区别

写完题目后,可输入主题的文本。输入时不用担心每行的宽度。 编译好的帮助文件会根据窗口大小自动确定行宽。在主题的最后插入一个分页符,WinHelp把每页视为一个单独主题。

  书写主题文本时应注意尽可能地把文本写成小段落列表,这样能方便阅读; 同时要控制主题长度,这样用户不需要使用滚动条来阅读文本。

  在主题中应加入一些脚标, WinHelp 使用这些脚标辨识主题并提供一些导向控制 (Novigation Control),四种典型脚标如表11.1所示。  

表11.1 脚标以及用途

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

   符号  适用于      用    途

──────────────────────────────────

   # 内容字符串    唯一辨识主题

   $ 标题       在搜询对话框和搜询历史列表框中显示主题

   K 关键字(段)    出在搜询对话框中

   + 浏览顺序     用户使用时的浏览顺序

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 

  以下分别介绍四种脚标的插入方法:

  1. 插入#脚标。把光标移至主题的最前端插入#脚标。这时主题文本下端也会出现#,在此后键入内容字符串。WinHelp使用内容字符串作为唯一的辨识主题。用户永远也看不见这些字符串,但设计人员用它们定义跳转主题。

  2. 插入$脚标,把光标移至#脚标后,插入$脚标。在文本下端的$脚标处,输入主题的标题,该标题与第一行出现的标题一致,标题将会出现在搜询对话框和搜询历史对话框中

3. 插入K脚标。在主题第一行的脚标之后插入K脚标,在主题文本中的K 脚标后键入字段,这些字段将出现在搜询列表框中,见11.3图。

  4.插入+脚标。在主题第一行的K脚标之后插入+脚标。在主题文本以下的+ 脚标处键入浏览顺序标识符。标识符可以是一个数(如005),或一组名字加上冒号和数(如 CAL C:005)一个主题只能有一个浏览顺序。

  热点是用户可以激发某种动作的文本或图像。一个热点可跳转至另一个主题。在其它窗口中显示主题或执行宏。多数情况下,重要字段被设计成热点以实现主题跳转。

  以下是实现主题跳转的步骤:

  1. 输入要跳转的字段或插入图像;

  2. 高亮度选择字段,用双下划线格式化。在MicroSoft Word中,按ALT +T 键弹出字符格式对话框,在列表中选择双下划线;

  3. 在紧挨在这些字段或图像之后,键入指定主题的字符串。 并对内容字符串进行隐藏格式化。这个内容字符串是跳转主题的内容字符串;

  根据以上步骤能实现主题之间的跳转。

  最后要把编辑的文件以RTF格式保存下来,WinHelp只能编译RTF文件。以下是典型的RTF文件: 

#$+ Help Example Indexindex_info 1 of 2index_2 

Commands

Edit Menumenu_edit

File Menumenu_file

Glossary

Defined Termsglossary

Procedures

Copying Textproc_copying_text

Deleting Textproc_deleting_text

Exitingproc_exiting

Available From Your Application

Context Sensitive Topics

 

 

 

 

 

 

cs_topics 

# main_index

$ Help Index

+ index:0005 

11.1.2.2 建立内容主题 

  内容主题列出了帮助系统的主要部分。用图标启动帮助系统或按Content按钮均出现内容主题。内容主题的每个项目都可跳转。

建立内容主题与建立一般主题类似,WinHelp默认第一个主题为内容主题。其建立步骤如下:

  1. 移至第一个源文件的开始处;

  2. 键入希望出现的主题标题,这些标题处于不同的行;

  3. 将每个主题设置成热点。 

11.1.2.3 建立帮助工程文件 

  帮助工程文件是一个文本文件。包含了有关帮助文件的许多信息。 编译器对工程文件进行编译。工程文件的扩展名必须是HPJ,编译后的扩展名是HLP:

下面是一个简单的帮助工程文件:

[OPTIONS]

CONTENTS=context_string

TITLE=title

COMPRESS=compress_level

ERRORLOG=log_filename

[CONFIG]

BrowseButtons()

[FILES]

RTF_filename_1

RTF_filename_2

RTF_filename_3

  [OPTIONS]

Context_String是内容主题的内容字符串。这一行并不是必须有的。 如果没有第一行,WinHelp把第一个帮助文件的第一个主题作为内容主题。

  TITLE = title

  title是帮助窗口的标题。不要将标题用引号括住。这一行也不是必需要有的。如果没有,缺省的标题是Windows Help.

CoMPRESS = Compress level

Compress_level决定工程文件在编译时是否被压缩, 压缩后的文件编译时要花较长的时间。

  表11.2 为Compress_level的取值: 

表11.2 Compress_leve的取值及含义

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  取值    编译时间       文件大小

─────────────────────────────

 FALSE 快          大(无压缩)

MEDIUM 中等         中等(高度压缩)

HIGH 慢          小(无压缩)

  0       快          大(无压缩)

 1       慢          小(高度压缩)

  No 快          大(无压缩)

 TRUE     慢          小(高度压缩)

  YES 慢           小(高度压缩)

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 

  ERROR(LOG = log_filename)

log_filename是WinHelp运行时的错误输出文件。如果编译时工程文件出错,需要一个记录错误的文件。如果工程文件有这一行,WinHelp在运行时自动创建的文件,如果没有,错误将显示在屏幕上,但不存入任何文件中。

  BrowseButton()

如果有这一行,帮助按钮条中将出现>>和<<键,要实现顺序浏览, 还需在帮助文件中定义。详见11.1.2.1节中的插入+脚标。

  [FILEs]

RTF_filename是.RTF源文件名。所有的RTF文件构成整个帮助系统。每个RTF 应处在

不同的行。

  以下是工程文件的实例

; This help project requires hc 3.1

[OPTIONS]

errorlog = iconwrks.err

title = IconWorks Help

contents = CONTENTS

compress = false

oldkeyphrase = false

warning = 3 

[FILES]

iconwrks.rtf 

[MAP]

CONTENTS 1

EDITOR_KEYBOARD 2

EDITOR_COMMANDS 3

VIEWER_KEYBOARD 5

VIEWER_COMMANDS 6

DEFINING_COLORS 1000

EDITOR_FILE_MENU 1100

EDITOR_FILE_MENU 1101

EDITOR_FILE_MENU 1102

EDITOR_FILE_MENU 1103

EDITOR_FILE_MENU 1104

EDITOR_FILE_MENU 1105

EDITOR_EDIT_MENU 1200

EDITOR_EDIT_MENU 1201

EDITOR_EDIT_MENU 1202

EDITOR_EDIT_MENU 1203

EDITOR_EDIT_MENU 1210

EDITOR_EDIT_MENU 1211

EDITOR_EDIT_MENU 1212

EDITOR_VIEW_MENU 1108

EDITOR_VIEW_MENU 1109

EDITOR_VIEW_MENU 1110

EDITOR_VIEW_MENU 1111

EDITOR_VIEW_MENU 1112

EDITOR_VIEW_MENU 1111

EDITOR_TOOLS_MENU 1400

SELECT_TOOL 1401

PAINT_TOOL 1402

FILL_TOOL 1403

LINE_TOOL 1404 

[WINDOWS]

main = "IconWorks Help", (0,0,1023,1023 ),,, (192,192,192 )

glossary = "IconWorks Help", (222,206,725,486 ),,, (192,192,192 ), 1 

[CONFIG]

CB("glossary", "&Glossary", "JI(`iconwrks.hlp>glossary', `GLOSSARY')")

BrowseButtons() 

11.1.2.4 编译帮助工程文件 

  有两种编译器可以编译帮助工程文件:HCP.EXE ,H31.EXE。两种编译器编译的文件不能在Winddow3.0中使用,但能在Windows 3.1中使用。其中HCP.EXE是保护模式“编译器”,它能更好的使用内存。必须在Windows的Dos窗口中使用HCP.EXE。

  编译前要注意两个问题:

  1. 所有源文件必须以RTF格式保存;

  2. 下面的文件必须在同一个目录下

   ● 所有的.RTF文件

   ● 帮助编译器(HCP.EXE,HC31.EXE)

● 编译器错误信息源文件(HCD.ERR,HC1.ERR)

   ● 帮助工程文件(.HPF)

● 任何引用位图或SHED的文件(.BMP.SHG) 

  如果以上文件不在同一目录中,必须在工程文件中定义相应的路径。

  编译要在Dos环境中进行,命令格式: 

  Help_Compiler_rootname project_File_rootname 

Help_Compiler_rootname是不带扩展名的编译器名字。project_file_rootname是不

带扩展名的帮助工程文件名,如: 

  HCP MYHELP 

11.2 Delphi应用程序的Help编程 

  Delphi应用程序能够方便地应用帮助系统。程序可以动态地运行帮助系统。 对话框可以与帮助系统相联。 

11.2.1 定义应用程序的帮助文件 

  要在应用程序中使用帮助系统,必须有相应的帮助文件。程序可以编写自己的帮助文件, 也可以使用已有的帮助文件。 另外,要为应用程序定义帮助文件以便在用户需要帮助时应用程序能打开相应的帮助文件。

  在 Delphi 集成开发环境中选择“Options | Project” 菜单项, 系统弹出工程选择对话框, 再选择Application Options页面,在辅助文件中输入帮助文件名。

 


想死你们了!

TOP

DELPHI基础教程

第十一章 Delphi应用程序的应用(二)
所有的应用程序都是TApplication的派生类。TApplication有三种方法调用在线帮助系统。

  HelpContext方法可调用WinHelp(关于Winhelp的内容见上节)。它把HelpFile 中的文件名和一个文本代码传递给WinHelp。HelpFile是TApplication的字符串类型的属性,专门用来存放Help文件的。如果HelpFile属性是空字符,HelpContext返回假值,其它情况均返回真值。

  下面的例子使用窗体上的一个按钮,当用户单击按钮,屏幕出现DATA.HLP文件中714号主题内容。 

  procedure TForm2.Bin1Click(Snder : TObject)

begin

Application.HelpFile := DATAHLP;

Application.HelpContext(714);

   end; 

        HelpJump方法可调用WinHelp。它传递HelpFile属性中的文件名和帮助文件的内容字符串(详见11.1节)。内容字符串是帮助文件中唯一辨识帮助主题的字符串。如果HelpFile 属性是空字符,HelpFJump返回假值,其它情况均返回真值。

  下面的例子使用了窗体上的一个按钮。当用户单击按钮, 帮助系统调出了 DELPH2.HLP文件中的Default属性。因为Default属性的内容字符串是VclDefaultProperty。 

procedure TForm1.Tbn|Click(Sender : TObject)

begin

Application.HelpFile := 'DELPHI.HLP';

Application.HelpJump ('VclDefaultProperty');

end 

HelpCommand方法能快速访问WinHelp函数中的各种命令。根据这些命令WinHelp执行不同的动作。表11.2是WinHelp函数的有关信息。

  BOOL WinHelp(hwd,LpszHelpFile,fuCommand,dwData) 

表11.2 WinHelp的参数及含义

 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  参数     类型     描 述

───────────────────────────────

  hwnd HWND   请求帮助的窗口

  LpszHelpFile LPSTK 待显示的帮助文件的文件名

  fuCommand UNIT 请求的帮助类型

  dwData DWORD 帮助所需的描述表或关键字

 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 

  HelpCOmmand向WinHelp传递fuCommand和dwData,fuCommand 是帮助类型可为表11.3中的列值之一。 

表11.3 fuCommand的取值及含义

 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  值          含 义

───────────────────────────────

  HELP_LONTEXT  显示dwData指定描述长的帮助信息

  HELP_CONTENTS  显示帮助的内容主题

  HELP_SET_LONTENTS 如果dwData是Orol04则在一个弹出

             式窗口中显示Help主题

  HELP_HELP PONHELP 显示Help应用程序的自身帮助,函

             数忽略lpszHelpFile和dwData参数

  HELP_INDEX 显示帮助文件的索引

  HELP_KEY 显示dwData指定的关键字的帮助

  HELP_MULTIKEY 显示一个关键字的帮助,该关键字

             在一个可变关键字表中

  HELP_QUIT 向Help应用程序报告文件不再使用

  HELP_SETNDEX 把dwData指定的描述符作为帮助文

              件的当前索引

 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 

  dwData参数的含义依赖于fuCommand的设置,如果fuCommand为HELP_CONTEXT,dwData为一个32 位的无符号整数,它包含一个描述表标识号:如果 fuCommand 为 HELP_KEY,dwData 则为一个指定长指针,所指的字符串是帮助的关键字。 如果 fuCommand 为HELPMULLTIKEY,dwData则指向一个MULTIKEYHELP数据结构的长指针。

  下面的例子使用了窗体中的按钮。 当用户单击按钮帮助系统将显示指定文件的帮助内容主题。 

  procedure TForm1.Bin1Click(Sender : TObject)

begin

Application.HelpFile := 'MyHlep.HLP'

Application.HelpCommand(HELP_CONTENTS,0);

end; 

TApplication部件的OnHelp事件响应帮助事件。 当应用程序接收到一个所需的帮助

时,发生OnHelp事件。使用OnHelp事件可以在需要帮助时定义一些特殊过程。 以下的例子改变了应用程序的帮助文件,AppHelp函数用来处理OnCreate事件。 

  function TForm1.AppHelp(Command.Word;Data : lontint) : Boolean

begin

if OpenDialog1.Exeeute then

Application HelpFile := OpenDialog1.FileName;

end; 

11.2.2 通用对话框中使用帮助系统 

  Delphi通用对话框中都能显示一个帮助按钮。如果程序显示了对话框中的帮助按钮,应该确保应用程序的帮助文件中有相应的主题。

  在通用对话框中使用帮助系统,要做到以下三点:

 1. 把对话框的Option|SHOWHelp属性设置成true,这样在程序运行时将出现帮助按钮。 ShowHelp 属性与其部件的名字相关, 例如字体对话框的 ShowHelp 属性称为fdShowHelp。

  2. 为对话框部件定义帮助文件。

3. 定义应用程序的文件名。  

11.3 Delphi帮助提示(Hint)的应用 

  使用Delphi集成开发环境时,用户常把鼠标置于程序部件上,如加速按钮,对齐按钮等。鼠标在部件上停留超过一定时间后,Delphi将会显示一个弹出窗口, 里面有部件名称和概述。

这就是Delphi的帮助提示。Delphi的应用程序可通过定义ShowHint 属性实现帮助提示。 

11.3.1 帮助提示的显示 

  ShowHint属性可应用于所有的控件和应用程序部件,控件的ShowHint 属性含义与程序的稍有不同。控件的ShowHint属性决定某一控件是否显示帮助提示,如果ShowHint 是真值,当用户把鼠标置于控件之上超过一定时间后,控件将出现帮助提示。如果是假值,则不出现提示。控件是否显示还决定于控件的ParentShowHind属性。如果 ParentShowHint是真值,控件的父类的ShowHint属性将决定控件是否显示帮助提示。 假如有一个分组框和一个检查框,分组框是检查框的父件。表11.3说明了子件与父件的 ShowHint,ParentShowHint属性设置对子件帮助提示的影响。 

表11.3 Hint属性设置对帮助提示的影响

 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  分组框ShowHint 检查框ParentShowHint 检查框ShowHint 帮助提示

─────────────────────────────────────

  T或F F T 显示

  T T F 显示

 F T T 不显示

  T或F         F F 不显示

 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

          T表示真值,F表示假值 

  把控件的ShowHint属性设置成真值,系统自动将ParentShowHint设置成假值。应用程序部件的ShowHint属性可以决定整个程序的帮助提示是否有效。如果应用程序的ShowHint 属性为真,程序中各部件的帮助提示才有效, 但是否显示还要取决于部件的SHowHint, ParentShowHint及其父件的ShowHint属性,如果应用程序的ShowHint属性为假值,无论程序部件的属性如何设置,程序中所有帮助提示都无效。

  Hint属性是显示在帮助提示框中的文本字符。Hint属性应用于所有控件,包括应用程序部件菜单部件。因为应用程序部件不是可视部件,因此不能在Object Inspector 窗口中定义Hint属性。但可以在定义部件的Hint属性时同时定义应用程序部件的Hint 属性,定义时只需用“|"字符会分开,例如: 

   Edit.Hint := 'Name |Enter Name in the edit box' 

等价于

   Edit.Hint := Name;

Application.Hint := Enter Name in the edit box 

应用程序的Hint属性可用在OnHint事件。

  如果只定义了一个值,Delphi把这个值同时赋给部件和应用程序部件的Hint属性。 如果应用程序的ShowHint属性为假值,所有的帮助提示将不显示, 但可以利用程序部件的Hint属性显示其它提示,如状态条等。

  当某一部件的SHowHint属性为真, 但又没有定义Hint 属性, 如果此时文件定义了Hint属性,则此部件将使用文件的Hint值。 

11.3.2 OnHint事件 

  当用户把鼠标放在某一部件,而该部件的Hint 值不为空值,此时发生OnHint事件。利用OnHint事件可以执行一些特殊的操作。

  最常用是利用OnHint事件显示状态条的标题,状态条是用面板来实现的。下面举例说明。

  这个例子使用了面板部件,菜单,一个编辑框。菜单可随意设计, 但需记住每个菜单项的Hint 值。 另外, 定义编辑框的Hint 值, 把面板置于窗体底部( 将Align 属性置于dBotton)把面板标题置于左端(将Alignment属性置于taleftJustify)。

  OnHint 事件是应用程序部件的事件,而应用程序部件是非可视部件,不能使用Object Inspector窗体定义事件,必须编写自己的OnHint事件。

  首先,在TForm1对象中宣称DisplayHint方法,并在单元的implementation部分编写实现代码。在DisplayHint方法中,把应用程序的Hint属性赋给面板的标题。另一个重要问题是必须把DisplayHint方法作为处理OnHint事件的方法。窗体的OnCreate事件的代码解决了这个问题。

  下面列出了程序的完整代码。当用户运行程序, 把鼠标置于菜单或部件之上,在窗体的状态条中将出现定义的提示。 

Type

TForm1 = class(TForm)

Button1: TButton;

Panel1: TPanel;

Edit1: TEdit;

procedure FormCreate(Sender: TObject);

private

{ Private declarations }

public

procedure DisplayHint(Sender: TObject);

end;

var

Form1: TForm1;

implementation

{$R *.FRM}

procedure TForm1.DisplayHint(Sender: TObject);

begin

Panel1.Caption := Application.Hint;

end;

procedure TForm1.FormCreate(Sender: TObject);

begin

Application.OnHint := DisplayHint;

end; 

11.4 自定义部件的帮助安装 

  Delphi有一个功能强大的帮助搜询引擎,叫做多层帮助,能为自定义的部件提供“上下文敏感”帮助。多层帮助允许把自定义部件的多个帮助文件安装成Delphi 的帮助序列,以提供给用户一种内层访问帮助文件的方式,用户有三种方法访问帮助文件:

  1. 设计状态选中部件,然后按F1;

  2. 在自定义部件的Object Inspector窗口中按F1;

  3. 在Delphi帮助系统选择搜询主题。

  Delphi 在提供这种帮助机制时, 不需要编写额外的代码。 有些文件是自定义部件帮助系统所必须的, 以下介绍安装的具体步骤。

  安装所需的文件

  STEREO.PAS     自定义部件的源代码

STEREO.RES     自定义部件的资源文件

STEREO.DCR     工具调色板图标0

STEREO.HRJ     帮助工程文件

STEREO.RTF     帮助源文件

STEREO.HLP     自定义部件的帮助文件

STEREO.KUF     关键字文件

  安装步骤 

11.4.1 安装关键字文件 

1. 退出Delphi集成开发环境

2. 备份\delphi\bin\delphi.hdx

3. 运行HelpInst应用程序

4. 打开\delphi\bin\delphi.hdx

5. 选择keywords |Add菜单项并选择Sberee.buf

6. 选择File|Source菜单项

7. 退出HelpInst

8. 因为WinHelp需要知道STEREO.HLP的位置所以要做以下其中之一:

    a. 把STEREO.Hlp复制到\delphi:\bin\目录下;

    b. 在WinHELP.INI文件中加上stereo.hlp=\usehelp; 

11.4.2 安装自定义部件 

1. 进入Delphi集成开发环境

2. 选择Option|Install Components菜单项

3. 选择Add

4. 选择Browse

5. 输入\stereo

6. 选择OK 

11.4.3 激活自定义部件帮助系统 

  1. TstereoButton和TStereeSpeaker部件从部件调色板上的Sample页拖至窗口;

2. 选择TStereoButton部件并按F1,屏幕上出现关于TStereoButton的帮助信息;

3. 在Object Inspector窗体口选择IsOn属性并按F1,屏幕显示IsOn属性;

4. 在主菜单中选择Help|Topic菜单项,并搜询Stereo 主题, 屏幕将出现STEREO

.HLP的帮助内容。


想死你们了!

TOP

DELPHI基础教程

第十二章 异常处理与程序调试(一)

  在应用程序开发中如何检测、处理程序的运行错误是一个很重要的问题。在 Delphi 的集成开发环境( IDE )中提供了一个完善的内置调试器,可以帮助你发现大部分程序错误。但并不是所有的错误都可以被发现,而且当程序涉及到与外设的数据交换或操作外设,如要求用户输入、读写磁盘等时,错误的发生是程序无法控制的,如输入非法字符、磁盘不能读写等。这些情况不仅会导致应用程序异常中止而且可能引起系统的崩溃。针对这些问题,Delphi同时提供了一套强大的异常处理机制。巧妙地利用它,可以使你的程序更为强健,使用更为友好。

  虽然Delphi为应用程序提供了一套缺省的自动异常处理机制,即当前模块发生错误后退出当前模块并给出错误信息,而并不立即引起应用程序的中止。但当应用程序执行的过程性很强时,仅仅利用这种方法是不够的,而且很容易导致程序执行的不可预测性。 

12.1 Delphi异常处理机制与异常类 

  Delphi异常处理机制建立在保护块(Protected Blocks)的概念上。所谓保护块是用保留字try和end封装的一段代码。保护块的作用是当应用程序发生错误时自动创建一个相应的异常类(Exception)。程序可以捕获并处理这个异常类,以确保程序的正常结束以及资源的释放和数据不受破坏。如果程序不进行处理,则系统会自动提供一个消息框。

  异常类是Delphi异常处理机制的核心,也是Delphi异常处理的主要特色。下面我们对异常类的概念和体系进行详细的介绍。

  Delphi提供的所有异常类都是类Exception的子类。用户也可以从Exception派生一个自定义的异常类。

  Exception类的定义如下,对于不常用的成员没有列出。  

{SysUtils 单元中}

Exception = class(TObject)

private

FMessage: PString;

FHelpContext: Longint;

function GetMessage: String;

procedure SetMessage(const Value: String);

public

constructor Create(const Msg: String);

constructor CreateFmt(const Msg: String; const Args: array of const);. . .

destructor Destroy; override;

property HelpContext: Longint

property Message: String;

property MessagePtr: PString;

end; 

Exception的一系列构造函数中最重要的参数是显示的错误信息。而数据成员中最重要的也是可被引用的消息字符串(message,messagePtr)。 这些信息分别对自定义一个异常类和处理一个异常类有重要作用。

  Delphi提供了一个很庞大的异常类体系,这些异常类几乎涉及到编程的各个方面。从大的方面我们可以把异常类分为运行时间库异常、对象异常、部件异常三类。下面我们分别进行介绍。 

12.1.1 运行时间库异常类(RTL Exception) 

  运行时间库异常可以分为七类,它们都定义在SysUtils库单元中。 

12.1.1.1 I/O异常 

  I/O异常类EInOutError是在程序运行中试图对文件或外设进行操作失败后产生的,它从Exception派生后增加了一个公有数据成员ErrorCode,用于保存所发生错误的代码。这一成员可用于在发生I/O异常后针对不同情况采取不同的对策。

  当设置编译指示{$I- } 时,不产生I/O异常类而是把错误代码返回到预定义变量IOResult中。 

12.1.1.2 堆异常 

  堆异常是在动态内存分配中产生的,包括两个类EOutOfMemory和EInvalidPointer。

表12.1  堆异常类及其产生原因

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

异常类 引发原因

─────────────────────────────────

EOutOfMemory 没有足够的空间用于满足所要求的内存分配

EInvalidPointer 非法指针。一般是由于程序试图去释放一个业已释 放的指针而引起的

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 

12.1.1.3  整数异常 

  整数异常都是从一个EIntError类派生的,但程序运行中引发的总是它的子类:EDivByZero,ERangeError,EIntOverFlow。 

   表12.2  整数异常及其产生原因

━━━━━━━━━━━━━━━━━━━━━

异常类 引发原因

─────────────────────

EDivByZero 试图被零除

ERangeError 整数表达式越界

EIntOverFlow 整数操作溢出

━━━━━━━━━━━━━━━━━━━━━━ 

  ERangeError当一个整数表达式的值超过为一个特定整数类型分配的范围时引发。比如下面一段代码将引发一个ERangeError异常。 

var

SmallNumber: ShortInt;

X , Y: Integer;

begin

X := 100;

Y := 75;

SmallNumber := X * Y;

end;

  特定整数类型包括ShortInt、Byte以及与整数兼容的枚举类型、布尔类型等。例如:  

type

THazard = ( Safety , Marginal , Critical , Catastrophic );

var

Haz: THazard;

Item: Integer;

begin

Item:= 4;

Haz:= THazard ( Item );

end; 

由于枚举数越界而引发一个ERangeError异常。

  数组元素越界也会引发一个ERangeError异常,如: 

var

Values: array[1..10] of Integer;

i: Integer;

begin

for i := 1 to 11 do

Values := i;

end;

ERangeError异常只有当类型检查打开时才会引发。这可以在代码中包含{$R+} 编译指示或设置IDE Option|Project的Range_Checking Option选择框。

  EIntOverFlow异常类在Integer、Word、Longint三种整数类型越界时引发。如:

var

I : Integer;

a,b,c : Word;

begin

a := 10;

b := 20;

c := 1;

for I := 0 to 100 do

begin

c := a*b*c;

end;

end;

引发一个EIntOverFlow异常。

EIntOverFlow异常类只有在编译选择框Option|Project|Over_Flow_Check Option选中时才产生。当关闭溢出检查,则溢出后变量保留该类整数的最大范围值。

整数类型的范围如下表。 

   表12.3 整数类型的范围

━━━━━━━━━━━━━━━━━━━━━━━━━━━

类型 范围 格式

  ───────────────────────────

Shortint -128 .. 127 有符号8位

Integer -32768 .. 32767 有符号16位

Longint -2147483648 .. 2147483647 有符号32位

Byte 0 .. 255 无符号8位

Word 0 .. 65535 无符号16位

━━━━━━━━━━━━━━━━━━━━━━━━━━━  

12.1.1.4 浮点异常 

  浮点异常是在进行实数操作时产生的,它们都从一个EMathError类派生,但与整数异常相同,程序运行中引发的总是它的子类EInvalidOp、EZeroDivide、EOverFlow、EUnderFlow。 

   表12.4 浮点异常类及其引发原因

━━━━━━━━━━━━━━━━━━━━━━━━

异常类 引发原因

────────────────────────

EInvalidOp 处理器碰到一个未定义的指令

EZeroDivide 试图被零除

EOverFlow 浮点上溢

EUnderFlow 浮点下溢

━━━━━━━━━━━━━━━━━━━━━━━━ 

  EInvalidOp最常见的引发原因是没有协处理器的机器遇到一个协处理器指令。由于在缺省情况下Delphi总是把浮点运算编译为协处理器指令,因而在386以下微机上常常会碰到这个错误。此时只需要在单元的接口部分设置全局编译指示{$N-},选择利用运行时间库进行浮点运算,问题就可以解决了。  

  各种类型的浮点数(Real、Single、Double、Extended)越界引起同样的溢出异常。这同整数异常类是不同的。 

12.1.1.5 类型匹配异常

  类型匹配异常EInvalidCast当试图用As 操作符把一个对象与另一类对象匹配失败后引发。 

12.1.1.6 类型转换异常

  类型转换异常EConvertError当试图用转换函数把数据从一种形式转换为另一种形式时引发,特别是当把一个字符串转换为数值时引发。下面程序中的两条执行语句都将引发一个EConvertError异常。

var

rl : Real;

int: Integer;

begin

rl := StrToFloat(' $140.48');

int := StrToInt(' 1,402 ');

end; 

要注意并不是所有的类型转换函数都会引发EConvertError异常。比如函数Val当它无法完成字符串到数值的转换时只把错误代码返回。利用这一点我们在(6.2)节中实现了输入的类型和范围检查。 

12.1.1.7 硬件异常

  硬件异常发生的情况有两种:或者是处理器检测到一个它不能处理的错误,或者是程序产生一个中断试图中止程序的执行。硬件异常不能编译进动态链接库(DLLs)中,而只能在标准的应用中使用。

  硬件异常都是EProcessor异常类的子类。但运行时间并不会引发一个EProcessor 异常。 

   表12.5  硬件异常类及其产生原因

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

异常类 引发原因

─────────────────────────────────

Efault 基本异常类。是其它异常类的父类

EGPFault 一般保护错。通常由一个未 初始化的指针或对象引起

EStackFault 非法访问处理器的栈段

EPageFault Windows内存管理器不能正确使用交换文件

EInvalidOpCode 处理器碰到一个未定义的指令。这通常意味着处理器

试图去操作非法数据或未初始化的内存

EBreakPoint 应用程序产生一个断点中断

ESingleStep 应用程序产生一个单步中断

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 

  EFault、EGPFault 往往意味着致命的错误。而EBreakPoint、ESingleStep被Delphi IDE的内置调试器处理。事实上前边的五种硬件异常的响应和处理对开发者来说都是十分棘手的问题。 

12.1.2 对象异常类 

  所谓对象异常是指非部件的对象引发的异常。Delphi定义的对象异常包括流异常、打印异常、图形异常、字符串链表异常等。 

12.1.2.1 流异常类 

  流异常类包括EStreamError、EFCreateError、 EFOpenError、EFilerError、EReadError、EWriteError、EClassNotFound。它们的结构关系如下: 

EStreamError

|---------- EFCreateError

|---------- EFOpenError

|---------- EFilerError

|--------- EReadError

|--------- EWriteError

|--------- EClassNotFound

    图12.1 流异常结构图 

流异常在Classes库单元中定义。

  流异常引发的原因如表12.6。

表12.6  流异常类及其产生原因

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

异常类 引发原因

─────────────────────────────────

EStreamError 利用LoadFromStream方法读一个流发生错误

EFCreateError 创建文件时发生错误

EFOpenError 打开文件时发生错误

EFilerError 试图再次登录一个存在的对象

EReadError ReadBuffer方法不能读取特定数目的字节

EWriteError WriteBuffer方法不能写特定数目的字节

EClassNotFound 窗口上的部件被从窗口的类型定义中删除

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 

12.1.2.2 打印异常类 

  打印异常类EPrinter当打印发生错误时引发。它在printers库单元中定义。例如你的应用程序试图向一个不存在的打印机打印或由于某种原因打印工作无法送到打印机时,就会产生一个打印异常。 

12.1.2.3 图形异常类 

  图形异常类定义在Graphic 库单元中,包括EInvalidGraphic和EInvalidGraphicOperation两类。

  EInvalidGraphic当应用程序试图从一个并不包含合法的位图、图标、元文件或用户自定义图形类型的文件中装入图形时引发。例如下面的代码: 

  Image1.Picture.LoadFromFile('Readme.txt'); 

  由于Readme.txt并不包含一个合法的图形,因而将引发一个EInvalidGraphic异常。

  EInvalidGraphicOperation当试图对一个图形进行非法操作时引发。例如试图改变一个图标的大小。 

var

AnIcon: TIcon;

begin

AnIcon := TIcon.Create;

AnIcon.LoadFromFile('C:\WINDOWS\DIRECTRY.ICO');

AnIcon.Width := 100; { 引发一个图形异常 }

...

12.1.2.4 字符串链表异常 

  字符串链表异常EStringListError、EListError在用户对字符串链表进行非法操作时引发。由于许多部件(如TListBox,TMemo,TTabSet,…)都有一个TStrings类的重要属性,因而字符串链表异常在部件操作编程中非常有用。

  EStringListError异常一般在字符串链表越界时产生。例如对如下初始化的列表框:  

ListBox1.Items.Add('First item');

ListBox1.Items.Add('Second item');

ListBox1.Items.Add('Third item');

  则以下操作都会引起EStringListError异常: 

ListBox1.Item[3] := ' Not Exist';

str := ListBox1.Item [3];

  EListError异常一般在如下两种情况下引发:

  1.当字符串链表的Duplicates属性设置为dupError时,应用程序试图加入一个重复的字符串;

  2.试图往一个排序的字符串链表中插入一个字符串。 

12.1.3 部件异常类 

12.1.3.1 通用部件异常类 

  通用部件异常类常用的有三个:EInvalidOperation、EComponentError、EOutOfResource。其中EInvalidOperation、EOutOfResource在Controls单元中定义;EComponentError在Classes单元中定义。

  1.非法操作异常 EInvalidOperation

  EInvalidOperation 引发的原因可能有:

  ● 应用程序试图对一个Parent属性为nil的部件进行一些需要Windows句柄的操作

  ● 试图对一个窗口进行拖放操作

  ● 操作违反了部件属性间内置的相互关系等 

  例如,ScrollBar、Gauge等部件要求Max属性大于等于Min属性,因而下面的语句: 

  ScrollBar1.Max := ScrollBar1.Min-1;

  将引发一个EInvalidOperation异常。 

  2.部件异常EComponentError

引发该异常的原因可能有:

  ● 在Register过程之外试图登录一个部件(常用于自定义部件开发中)

  ● 应用程序在运行中改变了一个部件的名称并使该部件与另一个部件重名

  ● 一个部件的名称改变为一个Object Pascal非法的标识符

  ● 动态生成一个部件与已存在的另一部件重名 

3.资源耗尽异常EOutOfResource

当应用程序试图创建一个Windows句柄而Windows 却没有多余的句柄分配时引发该异常。 

12.1.3.2 专用部件异常类 

  许多部件都定义了相应的部件异常类。但并不是有关部件的任何错误都会引发相应的异常类。许多情况下它们将引发一个运行时间异常或对象异常。

  下面列出几个典型的部件异常类。

  1.EMenuError

非法的菜单操作,例如试图删除一个不存在的菜单项。这一异常类在Menus库单元中定义。

  2.EInvalidGridOpertion

  非法的网格操作,比如试图引用一个不存在的网格单元。这一异常类在Grids库单元中定义。

  3.EDDEError

  DDE异常。比如应用程序找不到特定的服务器或会话,或者一个联接意外中止。这一异常类在DDEMan库单元中定义。

  4.EDatabaseError,EReportError

  数据库异常(EDatabaseError)和报表异常(EReportError) 在进行数据库和报表操作出现错误时引发。有关数据库的问题请读者参阅本书第二编。 

12.1.4 小结 

  在这一节中重点介绍了Delphi提供的异常类体系。我们力求给读者一个清晰、全面的印象,使读者能在自己的程序开发中实际使用它们。为便于理解我们也提供了一些简单的说明性示例。虽然在具体的使用中读者还可能会碰到许多问题,但意识到应该用异常类来增强程序的健壮性却是程序设计水平走上新台阶的标志。 

12.2 异常保护 

  确保回收分配的资源是程序健壮性的一个关键。但缺省情况下异常发生时程序会在出错点自动退出当前模块,因此需要一种特殊的机制来确保即使在异常发生的情况下释放资源的语句仍能被执行。而Delphi的异常处理正提供了这种机制。 

12.2.1 需要保护的资源 

  一般说来需要保护的资源包括:

  ● 文件

  ● 内存

  ● Windows资源

  ● 对象 

  比如下面一段程序就会造成1K内存资源的丢失。 

var

APointer : Pointer ;

AInt , ADiv: Integer ;

begin

ADiv := 0;

GetMem ( APointer , 1024 );

AInt := 10 div ADiv ;

FreeMem ( Apointer , 1024 );

end; 

由于程序从异常发生点退出从而FreeMem永远没有执行的机会。 

12.2.2 产生一个资源保护块 

  Delphi提供了一个保留字finally,用于实现资源的保护: 

  {分配资源}

  try

{资源使用情况}

finally

{释放资源}

  end; 

try…finally…end就形成了一个资源保护块。finally后面的语句是在任何情况下,不论程序是否发生异常,都会执行的。

  对于(12.2.1)中的例子如下代码即可确保所分配内存资源的释放: 

var

APointer : Pointer ;

AInt , ADiv : Integer;

begin

ADiv := 0;

GetMem ( APointer , 1024 );

try

AInt := 10 div ADiv ;

finally

FreeMem ( Apointer , 1024 );

end;

end; 

下面的例子摘自(6.4)节,是在文件拷贝中实现文件资源的保护: 

procedure CopyFile(const FileName, DestName: TFileName);

var

CopyBuffer: Pointer;

TimeStamp, BytesCopied: Longint;

Source, Dest: Integer;

Destination: TFileName;

const

ChunkSize: Longint = 8192;

begin

Destination := ExpandFileName(DestName);

if HasAttr(Destination, faDirectory) then

Destination := Destination + '\' + ExtractFileName(FileName);

TimeStamp := FileAge(FileName);

GetMem(CopyBuffer, ChunkSize);

try

Source := FileOpen(FileName, fmShareDenyWrite);

if Source < 0 then

raise EFOpenError.Create(FmtLoadStr(SFOpenError, [FileName]));

try

Dest := FileCreate(Destination);

if Dest < 0 then

raise EFCreateError.Create(FmtLoadStr(SFCreateError, [Destination]));

try

repeat

BytesCopied := FileRead(Source, CopyBuffer^, ChunkSize);

if BytesCopied > 0 then

FileWrite(Dest, CopyBuffer^, BytesCopied);

until BytesCopied < ChunkSize;

finally

FileClose(Dest);

end;

finally

FileClose(Source);

end;

finally

FreeMem(CopyBuffer, ChunkSize);

end;

end;

程序的具体解释见 (6.4)节。

  在异常保护的情况下,当异常发生时,系统会自动弹出一个消息框用于显示异常的消息。退出当前模块后异常类自动清除。


想死你们了!

TOP

DELPHI基础教程

第十二章 异常处理与程序调试(二)

12.3 异常响应 

  异常响应为开发者提供了一个按自己的需要进行异常处理的机制。try …except …end形成了一个异常响应保护块。与finally不同的是:正常情况下except 后面的语句并不被执行,而当异常发生时程序自动跳到except,进入异常响应处理模块。当异常被响应后异常类自动清除。

  下面的例子表示了文件打开、删除过程中发生异常时的处理情况: 

uses Dialogs;

var

F: Textfile;

begin

OpenDialog1.Title := 'Delete File';

if OpenDialog1.Execute then

begin

AssignFile(F, OpenDialog1.FileName);

try

Reset(F);

if MessageDlg('Erase ' +OpenDialog1.FileName + '?',

mtConfirmation, [mbYes, mbNo], 0) = mrYes then

begin

System.CloseFile(F);

Erase(F);

end;

except

on EInOutError do

MessageDlg('File I/O error.', mtError, [mbOk], 0);

on EAccessDenied do

MessageDlg('File access denied.', mtError, [mbOk], 0);

end;

end;

end.

  保留字on…do用于判断异常类型。必须注意的是:except后面的语句必须包含在某一个on…do模块中,而不能单独存在。这又是同finally不同的一个地方。 

12.3.1 使用异常实例 

  上面所使用的异常响应方法可总结为如下的形式: 

  on ExceptionType do

{响应某一类的异常} 

  这种方法唯一使用的信息是异常的类型。一般情况下这已能满足我们的需要。但我们却无法获取异常实例中包含的信息,比如异常消息、错误代码等。假设我们需要对它们进行处理,那么就必须使用异常实例。

  为了使用异常实例,需要为特定响应模块提供一个临时变量来保存它: 

  on EInstance : ExceptionType do  … 

  在当前响应模块中我们可以象使用一个普通对象那样来引用它的数据成员。但在当前响应模块之外不被承认。

  下面的代码用于获取异常消息并按自己的方式显示它: 

{窗口中包括一个ScrollBar部件,一个Button部件} 

procedure TErrorForm.Button1Click(Sender: TObject);

begin

try

ScrollBar1.Max := ScrollBar1.Min-1;

except

on E: EInvalidOperation do

MessageDlg('Ignoring Exception:'+E.Message,

mtInformation,[mbOK],0);

end;

end; 

12.3.2 提供缺省响应 

  在异常响应模块中,一般我们只对希望响应的特定异常进行处理。如果一个异常发生而响应模块并没有包含对它的处理代码,则退出当前响应模块,异常类仍被保留。

  为了保证任何异常发生后都能在当前响应模块中被清除,可以定义缺省响应: 

try

{程序正常功能}

except

on ESomething do

{响应特定异常}

else

{提供缺省响应}

end; 

由于else可以响应任何异常,包括我们一无所知的异常,因此在缺省响应中最好只包括诸如显示一个消息框之类的处理,而不要改变程序的运行状态或数据。 

12.3.3 响应一族异常 

  诸如

    on ExceptionType do

的异常响应语句不仅可响应本类异常,而且可以响应子类异常。对于象EIntError、EMathError等系统不会引发的异常,它们将只响应其子类异常。而对于象

   on Exception do

这样的语句将会对任何异常进行响应。

  下面一段代码对整数越界异常进行单独处理,而对其它整数异常进行统一处理: 

  try

{整数运算}

except

on ERangeError do

{越界处理}

on EIntError do

{其它整数异常处理}

end; 

由于异常在处理后即被清除,因而上面的代码可保证不会使ERangeError异常被多次处理。假如颠倒两条响应语句的顺序,则ERangeError异常响应将永远没有被执行的机会。 

  由于异常在处理后即被清除,因而当希望对异常进行多次处理时就需要使用保留字raise来重引发一个当前异常。

  下面的代码同时使用了异常响应和异常保护。异常响应用于设置变量的值,异常保护用于释放资源。当异常响应结束时利用raise重引发一个当前异常。 

var

APointer: Pointer ;

AInt , ADiv: Integer;

begin

ADiv := 0;

GetMem ( APointer , 1024 );

try

try

AInt := 10 div ADiv ;

except

on EDivByZero do

begin

AInt := 0 ;

raise;

end;

end;

finally

FreeMem ( APointer , 1024 );

end;

end;

  上面一段代码体现了异常处理的嵌套。异常保护、异常响应可以单独嵌套也可以如上例所示的那样相互嵌套。 

12.3.5 自定义异常类的应用 

  利用Delphi的异常类机制我们可以定义自己的异常类来处理程序执行中的异常情况。同标准异常不同的是:这种异常情况并不是相对于系统的正常运行,而是应用程序的预设定状态。比如输入一个非法的口令、输入数据值超出设定范围、计算结果偏离预计值等等。

  使用自定义异常需要:

  1.自己定义一个异常对象类;

  2.自己引发一个异常。 

12.3.5.1 定义异常对象类 

  异常是对象,所以定义一类新的异常同定义一个新的对象类型并无太大区别。由于缺省异常处理只处理从Exception或Exception子类继承的对象,因而自定义异常类应该作为Exception或其它标准异常类的子类。这样,假如在一个模块中引发了一个新定义的异常,而这个模块并没有包含对应的异常响应,则缺省异常处理机制将响应该异常,显示一个包含异常类名称和错误信息的消息框。

  下面是一个异常类的定义: 

  type

EMyException = Class(Exception) ; 

12.3.5.2 自引发异常 

  引发一个异常,调用保留字raise,后边跟一个异常类的实例。

  假如定义: 

type

EPasswordInvalid = Class(Exception); 

则在程序中如下的语句将引发一个EPasswordInvalid异常: 

 If Password <> CorrectPassword then

raise EPasswordInvalid.Create('Incorrect Password entered');

  异常产生时把System库单元中定义的变量ErrorAddr的值置为应用程序产生异常处的地址。在你的异常处理过程中可以引用ErrorAddr的值。

  在自己引发一个异常时,同样可以为ErrorAddr分配一个值。

  为异常分配一个错误地址需要使用保留字at,使用格式如下: 

  raise EInstance at Address_Expession; 

12.3.5.3 自定义异常的应用举例  

下面我们给出一个利用自定义异常编程的完整实例。

两个标签框(Label1、Label2)标示对应编辑框的功能。编辑框PassWord和InputEdit用于输入口令和数字。程序启动时Label2、InputEdit不可见。当在PassWord中输入正确的口令时,Label2、InputBox出现在屏幕上。此时Label1、PassWord隐藏。

设计时,令Label2、InputEdit的Visible属性为False。通过设置PassWord的PassWordChar可以确定输入口令时回显在屏幕上的字符。

自定义异常EInvalidPassWord和EInvalidInput分别用于表示输入的口令非法和数字非法。它们都是自定义异常EInValidation的子类。而EInValidation直接从Exception异常类派生。

下面是三个异常类的定义。 

type

EInValidation = class(Exception)

public

ErrorCode: Integer;

constructor Create(Const Msg: String;ErrorNum: Integer);

end;

EInvalidPassWord = class(EInValidation)

public

constructor Create;

end;

EInvalidInput = class(EInValidation)

public

constructor Create(ErrorNum: Integer);

end; 

EInValidation增加了一个公有成员ErrorCode来保存错误代码。错误代码的增加提供了很大的编程灵活性。对于异常类,可以根据错误代码提供不同的错误信息;对于使用者可以通过截取错误代码,在try...except模块之外来处理异常。

从以上定义可以发现:EInvalidPassWord和EInvalidInput的构造函数参数表中没有表示错误信息的参数。事实上,它们保存在构造函数内部。下面是三个自定义异常类构造函数的实现代码。 

constructor EInValidation.Create(Const Msg: String; ErrorNum: Integer);

begin

inherited Create(Msg);

ErrorCode := ErrorNum;

end;

constructor EInValidPassWord.Create;

begin

inherited Create('Invalid Password Entered',0);

end;

constructor EInValidInput.Create(ErrorNum: Integer);

var

Msg: String;

begin

case ErrorNum of

1:

Msg := 'Can not convert String to Number';

2:

Msg := 'Number is out of Range';

else

Msg := 'Input is Invalid';

end;

inherited Create(Msg,ErrorNum);

end; 

对于EInvalidInput,ErrorCode=1表示输入的不是纯数字序列,而ErrorCode=2表示输入数值越界。

口令检查是用户在PassWord中输入口令并按下回车键后开始的。实现代码在PassWord的OnKeyPress事件处理过程中: 

procedure TForm1.PassWordKeyPress(Sender: TObject; var Key: Char);

const

CurrentPassWord = 'Delphi';

begin

if Key = #13 then

begin

try

if PassWord.text <> CurrentPassWord then

raise EInvalidPassWord.Create;

Label2.Visible := True;

InputEdit.Visible := True;

InputEdit.SetFocus;

PassWord.Visible := False;

Label1.Visible := False;

except

on EInvalidPassWord do

begin

PassWord.text := '';

raise;

end;

end;

Key:=#0;

end;

end; 

同样,在InputEdit的OnKryPress事件处理过程中实现了输入数字的合法性检查: 

procedure TForm1.InputEditKeyPress(Sender: TObject; var Key: Char);

var

Res: Real;

Code: Integer;

begin

if Key = #13 then

begin

try

val(InputEdit.text,Res,Code);

if Code <> 0 then

raise EInValidInput.create(1);

if (Res > 1) or (Res < 0) then

raise EInValidInput.create(2);

MessageDlg('Correct Input', mtInformation,[mbOk], 0);

Key := #0;

except

on E:EInValidInput do

begin

InputEdit.text := '';

MessageDlg(E.Message, mtWarning,[mbOk], 0);

end;

end;

end;

end; 

由于异常响应后即被清除,所以要显示异常信息,需要另外的手段。在以上两段程序中我们采用了两种不同的方法:在口令合法性检查中,利用异常重引发由系统进行缺省响应;在输入数字合法性检查中,通过异常实例来获取异常信息并由自己来显示它。

以上所举的是一个非常简单的例子,但从中已可以发现:使用自定义异常编程,为程序设计带来了很大的灵活性。 

12.3.6 利用异常响应编程 

  利用异常处理机制不仅能使程序更加健壮,而且也提供了一种使程序更加简捷、明了的途径。事实上,使用自定义异常类就是一种利用异常响应编程的方式。这里我们再讨论几个利用标准异常类编程的例子。

  比如为了防止零作除数,可以在进行除法运算前使用if…then…else语句。但如果有一系列这样的语句则繁琐程度是令人难以忍受的。这时候我们可能倾向于使用EDivByZero异常。例如如下一段程序就远比用if…then…else实现简捷明了。 

function Calcu(x,y,z,a,b,c:Integer):Real;

begin

try

Result := x/a+y/b+z/c ;

except

on EDivByZero do

Result := 0;

end;

end;

在(6.2.3)记录文件的打开与创建中就是利用异常响应来实现文件的打开或创建。 

procedure TRecFileForm.OpenButtonClick(Sender: TObject);

begin

if OpenDialog1.Execute then

FileName := OpenDialog1.FileName

else

exit;

AssignFile(MethodFile,Filename);

try

Reset(MethodFile);

FileOpened := True;

except

on EInOutError do

begin

try

if FileExists(FileName) = False then

begin

ReWrite(MethodFile);

FileOpened := True;

end

else

begin

FileOpened := False;

MessageDlg('文件不能打开',mtWarning,[mbOK],0);

end;

except

on EInOutError do

begin

FileOpened := False;

MessageDlg('文件不能创建',mtWarning,[mbOK],0);

end;

end;

end;

end;

if FileOpened = False then exit;

Count := FileSize(MethodFile);

if Count > 0 then

ChangeGrid;

RecFileForm.Caption := FormCaption+' -- '+FileName;

NewButton.Enabled := False;

OpenButton.Enabled := False;

CloseButton.Enabled := True;

end; 

总之,利用异常响应编程的中心思想是虽然存在预防异常发生的确定方法,但却对异常的产生并不进行事前预防,而是进行事后处理,并以此来简化程序的逻辑结构。 

12.4 程序调试简介 

  Delphi提供了一个功能强大的内置调试器(Integrated Debugger), 因而对程序的调试不用离开集成开发环境(IDE)就可以进行。

  程序错误基本可以分为两类,即运行时间错和逻辑错。所谓运行时间错是指程序能正常编译但在运行时出错。逻辑错是指程序设计和实现上的错误。程序语句是合法的,并顺利执行了,但执行结果却不是所希望的。

  对于这两类错误,调试器都可以帮助你快速定位错误,并通过对程序运行的跟踪和对变量值的监视帮助你寻找错误的真正原因和解决错误的途径。

  程序调试的主要内容可以概括为如下的几方面:

  1.调试的准备和开始;

  2.控制程序的执行;

  3.断点的使用;

  4.检查数据的值。

  程序调试只有用户实际上机操作才能真正掌握。在这一节中我们主要对调试中的主要问题和一些关键点进行介绍。至于一些很细小的问题相信读者可以在上机实际应用中掌握,因而没有列出。

 

12.4.1 调试的准备和开始 

  在程序开发过程中程序编码和调试是一个持续的循环过程,只有在你对程序进行了彻底的测试后才能交付最终用户使用。为了保证调试的彻底性,在调试前应制定一个详细的调试计划。一般说来应该把程序划分为几个相对独立的部分,分别进行调试,以利于错误的迅速定位,确保每一部分程序都按设计的要求运行。

  调试计划准备好后就可以开始程序的调试。

  开始一个调试过程包括:

  1.编译时产生调试信息;

  2.从Delphi里运行你的程序。

  在程序调试过程中,程序的执行完全在你的控制之中。你可以在任何位置暂停程序的执行去检查变量和数据结构的值,去显示函数调用序列,去修改程序中变量的值以便观察不同值对程序行为的影响。 

12.4.1.1 产生调试信息 

  要使用内部调试器必须选中Option| Environment菜单References页的Integrated Debugging检查框。缺省情况下该框被选中。

  在开始调试前需要使用Symbols Debug Information(调试符号信息)编译工程文件。调试符号信息包含了一个符号表,能够使调试器在程序的源代码与编译器产生的机器代码间建立联系。这样在程序执行中可以同时查看对应的源代码。

  Delphi 在缺省情况下自动产生调试符号信息。在集成开发环境中的开关选项是Option|project菜单Compiler Options页的Debug Information and Local Symbols检查框。

  当产生的调试符号信息供内部调试器使用时,编译器把调试符号表储存在每个相应的.dcu文件中。

  如果希望在集成环境外使用Turbo Debugger,则需要把调试信息储存在最终的 .exe文件中。为此需要选定Option|Project菜单Linker页的Include TDW Debug Info检查框。

  由于储存调试信息大大增加了执行文件的大小,因而调试完成后应重新生成一个不包含调试信息的执行文件。 

12.4.1.2 运行程序 

  通过调试器(包括内置调试器)运行程序,当程序处于等待状态时,调试器可以获得控制,利用调试器的功能来检查当前程序的状态。通过合理布置屏幕显示,使应用程序运行窗口和Code Editor(代码编辑器)互不重叠,可以让用户在它们间方便地切换以观察代码执行的效果。

  如果希望使用命令行参数来调试程序,则可以通过Run|Parameters 菜单打开运行参数对话框进行设置。 

12.4.2 程序运行的控制 

  程序运行控制的方法和使用如下表。 

   表12.7  程序运行控制的方法和使用途径

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

方法 使用途径

───────────────────────────────

运行到光标位置 ● Code Editor加速菜单的Run to Cursor项

(Run to Cursor) ● Run主菜单的Run to Cursor项

● F4

跟踪(Trace Into) ● Run主菜单的Trace Into项

● Trace Into加速按钮

● F7

步进(Step Over) ● Run主菜单的Step Over项

● Step Over加速按钮

● F8

运行到断点 设置断点并按正常方式运行

暂停程序执行 Run主菜单的Program Pause项

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 

  跟踪和步进都是一种单步执行方式。但“步”的含义不同。对跟踪而言它一次执行一条简单程序语句。当碰到包含调试信息的函数或过程调用时则跳入该函数或过程,并执行其第一条可执行语句。对步进而言它一次执行一条当前模块的可执行语句,而不管该语句是否是函数或过程调用。

  运行到光标位置和运行到断点都是程序正常运行到某一确定的源代码位置,而后进入调试状态。但相对于运行到光标位置而言,运行到断点更为灵活。因为断点一次可设置多个,同时也可以对断点设置一定的条件。只有满足该条件程序运行才会中止。


想死你们了!

TOP

DELPHI基础教程

第十二章 异常处理与程序调试(三)

12.4.3 断点的使用 

12.4.3.1 设置断点 

  设置断点首先在Code Editor中选定你想设置断点的代码行,而后进行如下的任一种操作:

  ● 单击选定代码行左边的空白

  ● 按F5

  ● 选择Code Editor加速菜单的Toggle BreakPoint项

  ● 选择Run|Add Breadpoint打开断点编辑对话框(Edit BreakPoint Dialog Box),而后选择New去确认一个新的断点设置或选择Modify去对一个存在的断点进行修改

  ● 从BreakPoint List加速菜单中选择Add BreakPoint项 

  断点必须位于可执行代码行上,凡设置在注释、空白行、变量说明上的都是无效的。另外,断点既可以在设计状态下设置也可以在运行调试状态下设置。 

12.4.3.2 断点的操作 

  断点列表窗口(BreakPoint List Window)列出了所有断点所在的源文件名、行号、条件以及已通过的次数。如果一个断点非法或失去功能,则在列表窗口中变灰。

  断点列表窗口可以通过选择View|BreakPoint菜单打开。

断点列表窗口是断点操作的基础。

  1.显示和编辑断点处的代码

  利用断点列表窗口可以快速找到断点在源代码中的位置。

  首先选定断点而后从加速菜单中选择View Source或Edit Source。此时Code Editor更新,显示该断点位置处的代码。如果选择的是View Source,则断点列表窗口仍保持活动;如果选择的是Edit Source,则Code Editor获得输入焦点,可以在断点位置修改源代码。

  2.断点功能的丧失和恢复

  使断点失去功能可以使断点从当前程序运行中隐藏起来。假如你定义了一个断点当前并不需要,但可能在以后使用,则这一功能是很有用的。

  断点列表窗口加速菜单的Disable BreakPoint和Disable All BreakPoints项可以使当前选中断点或所有断点失去功能。

  加速菜单中的Enable BreakPoint和Enable All BreakPoint 可以使相应断点恢复功能。

  3.断点的删除

  断点删除可以从Code Editor或断点列表窗口中进行。

  从Code Editor:

  ● 把光标停到包含断点的行并按F5(或选择加速菜单的Toggle BreakPoint)

  ● 单击包含断点行左边的终止符 

  从断点列表窗口:

  ● 选中欲删除的断点并选择加速菜单的Delete BreakPoint项

  ● 删除当前所有断点,则选择加速菜单的Delete All BreakPoints项 

12.4.3.3 修改断点属性 

  断点列表窗口双击选定断点或从加速菜单中选择Edit BreakPoint项,可以打开断点编辑对话框,用于显示和修改断点的属性。

利用断点编辑对话框可以改变断点的位置,设置断点条件。

  断点条件包括两种:布尔表示式和通过次数。

  Condition编辑框用于设置布尔表达式条件。如果表达式值为真(或非零)则程序运行在断点处中止;否则调试器将忽略该断点。

  Pass Count编辑框用于设置通过次数条件,即只有当程序运行在该断点处通过设定次数时程序运行才在该断点处中止。这往往用于对循环体内语句的调试。

  有一点应引起注意的是:当Condition和Pass Count同时设置时,Pass Count是指满足条件的通过次数。

  对如下一段程序: 

var

i,Re,s: Integer ;

begin

s := 1;

Re := 0;

for i:=1 to 100 do

Re:=Re+s*i ;

end; 

在 Re := Re + s*i; 一行设置一断点。

若条件设置为: 

  Condition :  i = 3

Pass Count:   4

  则当程序中止时检测i 的值为7。 

12.4.3.4 断点和程序执行点颜色的设置 

  选择Option|Environment进入环境设置对话框而后选择Editor Colors页标签。此时即可对有关项按自己的希望设置背景和前景色。 

12.4.4 监视数据的值 

  内置调试器提供了如下的工具用于监视程序中数据的值:

  ● 监视列表窗口

  ● 计算/修改对话框

  ● 调栈窗口 

12.4.4.1 监视表达式 

  监视列表窗口(Watch List Window)显示程序运行中当前监视表达式的值。

  选择View|Watches可以打开监视列表窗口。

从Code Editor中添加一个监视表达式最方便的方法是:

  1.选中要监视的表达式;

  2.从Code Editor加速菜单中选择Add Watch把表达式添加到监视列表窗口。

  也可以利用下面的方法产生一个监视表达式:

  1.用下列方法之一打开监视属性对话框(Watch Properties Dialog Box):

●主菜单中选择Run|Add Watch

●在光标处从Code Editor加速菜单中选择Add Watch

  ●按Ctrl-F5

  ●双击监视列表窗口中的一个监视表达式

  ●从监视列表窗口选定一个表达式而后从加速菜单中选择Edit

  2.在监视属性对话框的Expression下拉列表框中输入或选择一个被监视的表达式;

  3.设定表达式的显示格式和使能状态。

  与断点类似,利用加速菜单也可以使监视表达式功能丧失、恢复或删除监视表达式。 

12.4.4.2 计算/修改表达式 

 选择Run|Evaluate /Modify可打开计算/修改对话框。当单击Evaluate按钮时,Expression编辑框中表达式的值显示在Result域中。

  Expression中可以输入或选择任何合法的表达式(包括对象的属性),但不包括;

  1.包含有当前执行点不能引用的局部或静态变量的表达式;

  2.函数或过程调用。

  Expression中的表达式可以带特定的格式字符用于规定其显示格式。 格式字符及其功能如下表。 

   表12.8  格式字符及其功能

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

格式字符 功 能

─────────────────────────────────

$,H,X 以十六进制格式显示标量

D 以十进制格式显示标量

C 把ASCII码在0..31的特殊字等显示为ASCII码图形

Fn 用n个有效数字显示浮点数

M 以十六进制方式显示一变量的内存转储值

P 以段和偏移量格式显示指针。两部分皆为四位十六进制值

R 显示记录、对象的域名和值(例如 X:5,Y:2)

S 用ASCII码显示字符串(包括特殊字符)。用于修改内存转储值

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  修改表达式的值常用于验证错误解决方案的正确性。在Expression编辑框中输入或选定欲修改的表达式,单击Evaluate按钮观察表达式的当前值。而后在New Value编辑框中输入或选中一个新值,并单击Modify按钮确认并更新数据项。这种修改只影响特定的程序运行。

  修改表达式的值(特别是指针变量和数组下标)可能会引起无法预计的后果。因而使用中要特别小心。 

12.4.4.3 显示函数调用 

  选择View|Call Stack可以显示调栈窗口(Call Stack Window)。调栈窗口的顶端列出了应用程序最近的函数调用。

利用调栈窗口可以退出当前跟踪的函数,可以利用加速菜单项显示或编辑位于特定函数调用处的源代码

12.5 其它调试工具 

  Delphi的内置调试器虽然功能很强大,但并不能胜任所有的任务。同时由于内置调试器在执行中引起程序环境的细微变化,所以可能影响错误的发生方式。为此我们需要使用其它调试工具来完成我们的任务。这些调试工具包括Turbo Debugger、WinSight、WinSpector和Browser。Browser将在下一节中专门进行介绍。 

12.5.1 Turbo Debugger 

Turbo Debugger是Borland公司推出的第三代语言调试器,它虽然还没有推出完全支持Delphi的新版本,但也基本能胜任一般Delphi程序的调试。

  Turbo Debugger在字符模式下执行,但它是一个真正的Windows程序,它仅使用基于字符的界面。由于Turbo Debugger是一个准备控制其它程序的特殊程序,因此不可以使用通常Windows任务切换功能如Alt+Tab。

Turbo Debugger的操作大部分与内置调试器相同或类似。利用File|Open菜单装入要调试的文件就可以开始一个调试过程。

  利用Turbo Debugger必须把调试符号信息储存在可执行文件中。具体操作见(12. 4.1.1)中介绍。

  Turbo Debugger与内置调试器相比,有许多新的功能。

  首先它提供了许多在低级硬件信息方面的控制。可以完全访问CPU的寄存器、标志及系统内存。用户可以跟踪远指针到内存位置并直接检查它们的内容。Turbo Debugger可以跟踪进到代码中,即使得不到源代码也可以。

  Turbo Debugger支持许多Windows的特殊功能。它可以跟踪Windows消息,让用户查看程序的局部堆和全局堆,并可以浏览包括DLLs在内的组成程序的所有代码单元列表。

  另外Turbo Debugger支持远程执行能力。可以通过串口链接或通过支持NetBIOS的网络配置Turbo Debugger控制另外一台机器。在这种模式下,一台机器显示调试器屏幕,另一台机器显示被跟踪的程序。这允许在一个屏上单步执行程序并在另一屏上监视结果。 

12.5.2 WinSight

  WinSight 是一个用于查看Windows 对象并跟踪消息的发送和接收的调试工具。WinSight的图标可以在Delphi程序组中找到。

WinSight界面分为两部分,上面为对象树窗口,下面为消息跟踪窗口。如图12.9所示。

  在实际应用中,用户可能只是对其中的一部分消息感兴趣,而又不希望它们淹灭在无用信息之中。为此,用户可以打开Message菜单并选择Selected Windows。按住Shift键,单击对象树窗口中感兴趣的对象,所选定对象的任何消息都显示在消息跟踪窗口中。

  如果用户只想跟踪某些消息类,则打开Message菜单并选择Options ,使用如图12.10所示的检查框过滤消息。 

12.6.6 小结 

  本章介绍的内容,核心是如何增强程序的健壮性并提高开发效率。为此我们首先考察了Delphi的异常处理机制,而后介绍了几种程序调试工具,在您即将结束基础篇的学习时,这些内容是您步入开发大型应用程序的高级程序员行列的必备武器。


想死你们了!

TOP

DELPHI基础教程

第十三章 Delphi开发数据库应用程序概述(一)

13.1 数据库系统概述 

        数据库系统为我们提供了一种把与我们的工作和生活紧密相关的信息集合在一起的方法,它还提供了在某个集中的地方存储和维护这些信息的方法。数据库系统主要由三大部分组成:数据库管理系统(DBMS:它是专门负责组织和管理数据信息的程序)、 数据库应用程序(它使我们能够获取、显示和更新由DBMS存储的数据)、数据库(按一定结构组织在一起的相关数据的集合)。

        一般来说,DBMS和数据库应用程序都驻留在同一台计算机上并在同一台计算机上运行,很多情况下两者甚至结合在同一个程序中,以前使用的大多数数据库系统都是用这种方法设计的。但是随着DBMS技术的发展,目前的数据库系统正向客户/服务器模式发展。客户/服务器数据库将DBMS和数据库应用程序分开,从而提高了数据库系统的处理能力。数据库应用程序运行在一个或多个用户工作站(客户机)上,并且通过网络与运行在其它计算机上(服务器)的一个或多个DBMS进行通信。

  下面是数据库系统中一些概念和述语。 

13.1.1 数据库管理系统(DBMS) 

        数据库管理系统(DBMS)是用于描述、管理和维护数据库的程序系统,是数据库系统的核心组成部分。它建立在操作系统的基础上,对数据库进行统一的管理和控制。其主要功能有:

1. 描述数据库:描述数据库的逻辑结构、存储结构、语义信息和保密要求等。

2. 管理数据库:控制整个数据库系统的运行,控制用户的并发性访问,检验数据的安 全、保密与完整性,执行数据检索、插入、删除、修改等操作。

3.维护数据库:控制数据库初始数据的装入,记录工作日志,监视数据库性能,修改更新数据库,重新组织数据库,恢复出现故障的数据库。

4.数据通信 :组织数据的传输。

        DBMS主要有四种类型:文件管理系统、层次数据库系统、 网状数据库系统和关系数据库系统。因为目前关系数据库系统应用最为广泛,所以我们重点对关系数据库系统中的几个概念进行介绍。

        关系数据库(Relational Database):一个关系数据库是由若干表组成。在Delphi中,数据库概念对应到物理文件上是有一些不同的。对于dBASE、FoxPro、Paradox这三种数据库系统,数据库对应于某一个子目录,而其它类型如MS Access、Btrieve则是指某个文件。这是因为前者的表为单独的文件,而后者的表是聚集在一个数据库文件中的。

        表(Table):一个表就是一组相关的数据按行排列,象一张表格一样。比如一个班所有学生的期末考试成绩,存在一个表中,每一行对应一名学生,在这一行中,包括学生的学号、姓名以及各门课程的成绩。

        字段(Field):在表中,每一列称为一个字段。每一个字段都有相应的描述信息,如数据类型、数据宽度等。

记录(Record):在表中,每一行称为一条记录。

索引(Index):为了加快访问数据库的速度,许多数据库都使用索引。 

13.1.2 数据库应用程序

         DBMS中存储了大量的数据信息,其目的是为用户提供数据信息服务,而数据库应用程序正是与DBMS进行通信,并访问DBMS中的数据,它是DBMS实现其对外提供数据信息服务这一目的的唯一途径。简单地说,数据库应用程序是一个允许用户插入、修改、删除并报告数据库中的数据的计算机程序。数据库应用程序在传统上是由程序员用一种或多种通用或专用的程序设计语言编写的,但是近年来出现了多种面向用户的数据库应用程序开发工具,这些工具可以简化使用DBMS的过程,并且不需要专门编程。Delphi就是一种强有力的数据库应用程序开发工具。

用来生成数据库应用程序的语言主要分为三大类型:

1.过程化语言

        标准的计算机程序设计语言如Pascal、Basic和C都是过程化语言,这些语言可以通过某种“应用程序接口”(API)来创建数据库应用程序,这种API由一组标准的函数(或调用)组成,这些函数和调用则扩展了语言的功能,使之能访问数据库中的数据。当程序设计人员用过程化语言创建数据库应用时,必须把应用的代码编写成一系列的过程,每个过程执行应用的某一部分的工作,如一个过程查询数据库,而另一过程更新数据库中的数据,然后不同的过程通过其他的用户界面过程(例如菜单系统)联系在一起,并且在应用中的适当地方运行。

        上述这些过程化语言一般用来创建非数据库应用程序,它们通常被称为“第三代语言”(3GL)。还有一些过程化程序设计语言是某种特定的DBMS专用的, 这些语言一般被称为“第四代语言”(4GL),即数据库专用语言。常见的数据库专用的过程化语言如dBASE语言,Paradox数据库的PAL语言等等。

2.结构化查询语言(SQL)

       结构化查询语言(Structure Query Language)是基于关系模型的数据库查询语言,它是一种非过程化的程序语言,也就是说,没有必要写出将如何做某事情,只需写出做到什么就可以了。写出的语句可看作是一个问题,称为“查询”(Query),针对这个查询,得到所需的查询结果。下面是一个例子: 

Select Name,Total from Class where Total>600 

        这个查询意为从数据库表Class中将总分(Total)大于600的所有人选出来, 并列出他们的姓名(Name)和总分(Total)。

        把SQL描述为子语言更适当一些,因为它没有任何屏幕处理或用户输入/输出的能力。它的主要目的是为了提供访问数据库的标准方法,而不管数据库应用的其余部分是用什么语言编写的,它既是为数据库的交互式查询而设计的(因此被称为动态SQL), 同时也可在过程化语言编写的数据库应用程序中使用(因此被称为嵌入式SQL)。

3.其他语言

        用于开发数据库应用程序的语言中,还可以使用目前数常见的“面向对象程序设计”(OOP)语言,如C++、 Objact Pascal等,OOP代表了一种完全不同的程序设计方法, 在这种程序设计方法中,活动被定义为在“对象”上发生的操作,而不是作为一系列过程来定义的。在数据库应用程序中使用OOP语言的情况正在不断增加。

        开发数据库应用程序使用的另一种语言是“宏”语言。宏语言不是一种完全的程序设计语言,它实际上是一个用户手工输入的表,这个表被输入到应用程序中,以便自动执行一定的任务。对于某个特定应用的高级语言,宏语言通常可以在低档DBMS软件中或数据库服务器的前端中找到。

         最后,还有一种“Query-By-Example”(QBE,范例查询)语言。严格地讲QBE不是一种语言,它是面向用户提供了一个或多个空表的界面,这些空表对应于数据库中的表。用户可以通过键盘选择需要查询的列,并在适当的列中填入条件从而定义查询的检索条件,然后DBMS就把QBE转换成相应的动作,以完成用户要求的查询任务。 

13.2 Delphi的数据库特性及功能简介 

        直到目前为止,计算机软件的开发分为两个不同的体系,其中一个体系是使用传统的程序设计语言(如Pascal、Basic和C等)开发数值控制、数值运算等软件,围绕它们的重点是算术、数据结构以及近年产生的面向对象技术。另一个体系则是通用的数据库管理软件领域(数据库应用程序的开发)。这两个体系的发展都极为迅猛,但是二者并没出现混合渗透迹象。如果使用数据库语言进行传统的算术编程,虽然也能完成相应的功能,但是其编程过程可能极为复杂。如果使用传统的编程语言进行数据库编程,通过调用专用的数据库应用程序接口函数和过程,利用这些函数和过程提供的功能,可能也能做得比较完善,但这做起来大多是极其困难的。而Delphi结合了两个体系的优点,它结合了传统的编程语言Object Pascal和数据库语言的强大功能, 它即可以用于传统的算术编程又可以用于数据库编程,特别是Delphi具有强大的数据库功能,利用Delphi的数据库工具,我们根本不需要编写任何Object Pascal代码便可以创建一个简单的数据库应用。

        Delphi是Borland公司于1994年底发布的用于开发数据库应用程序的工具, 它是面向对象的,它是目前开发客户/服务器数据库应用程序的强有力的工具。Delphi在Window3.1以上版本的系统环境下运行,目前具有两个版本:Delphi的标准版本和客户/服务器版本。标准版本包含一个Borland Database Engine的局部拷贝,它允许用户创建能访问dBASE、Paradox和Local InterBase 服务器的数据库应用, 它还支持具有 ODBC 接口的数据库。Delphi的客户/服务器版本包括Borland SQL Link, 它能直接访问 ORACLE 、 SyBase 和Microsoft SQL Server,Informix以及InterBase数据库服务器。

        Delphi可以访问多种数据库管理系统的数据库,凭借窗体(Forms)和报表(Reports),BDE(Borland Database Engine)可以访问诸如Paradox、dBASE、本地InterBase 服务器的数据库,也可以访问远程数据库服务器上的数据库(如ORACLE、SyBase、Informix等客户/服务器数据库中的数据库),或任何经ODBC(Open Database Connecticity) 可访问的数据库管理系统中的数据库。 

13.2.1 Delphi的数据库特性 

        跟其他的应用程序一样,Delphi提供了许多部件以方便地创建数据库应用程序。数据库对象的数据成员既可在设计阶段设置,也可在运行阶段通过程序代码进行设置。Delphi的部件板上提供了两页数据库应用程序开发中所要使用的部件:

        数据访问页(Data Access Page)上的部件用于直接访问数据库中的数据库表。

        数据控制页(Data Control Page)上的部件用来与用户交互,显示、 修改数据库中的数据。

数据库应用程序首先是利用Delphi提供的数据库部件与BDE建立联系,然后再通过BDE与数据库联系。下图阐述了Delphi的数据库工具和部件、Delphi数据库应用程序与BDE 、数据源之间的关系。

下表概括了Delphi的数据库特性: 

表13.1 Delphi的数据库特性

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  工具和部件   主 要 用 途  

────────────────────────────────────── 

 Data Access Components  访问数据库、数据库表、存贮过程等  

────────────────────────────────────── 

 Data Control Components  与用户交互,提供显示、修改数据库中数据的界面  

────────────────────────────────────── 

 Database Desktop(DBD)  建立、索引、查询数据库表以及访问、编辑来自各数据 

   中的数据  

────────────────────────────────────── 

 ReportSmith  建立、浏览和打印数据库表中的数据  

────────────────────────────────────── 

 Borland Database Engine  数据库应用通过BDE访问dBASE Paradox数据库中的数据 

  (BDE)  和本地InterBase数据库服务器中的数据

 

────────────────────────────────────── 

  BDE Configuration  建立和管理BDE与数据库建立连接时所使用的数据库的  

  Utility  别名  

────────────────────────────────────── 

   它是一个单用户、多例程的本地SQL数据库服务器,可  

 Local InterBase Server  在单机环境下用来开发或测试客户/服务器数据库应用  

   程序,然后再将之扩展成一个访问远程数据库服务器如 

   ORACLE、SyBase、Informix等  

────────────────────────────────────── 

 InterBase SQL Link  连接Delphi数据库应用程序一本地InterBase服务器的  

   驱动程序  

Delphi上述这些特性使得我们创建数据库应用程序通过BDE能够很灵活地与 dBASE 、Paradox、Local InterBase数据库服务器进行连接并可以方便地访问其中的数据。我们在创建一个简单的数据库应用时通过使用Delphi提供的上述工具和部件甚至可以不需编写任何程序。

BDE被自动地包含在Delphi中,因此,我们在创建数据库应用程序时,不必关心BDE的有关内容。Delphi的安装程序自动为Paradox、dBASE和本地InterBaseServer 安装相应的驱动程序,并建立了有关的配置,DBE Configuration Utility 可以建立应用程序与数据库的连接信息,还可以为数据库设置别名。

下表列出了Delphi开发Client/Server应用程序的有关特性,这些特性扩展了 Delphi访问远程数据库的功能,如SQL数据库服务器(ORACLE、SyBase、Informix、 Microsoft SQL Server、InterBase)。 

表13.2 Delphi Client/server数据库特性

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

工 具   主 要 用 途

─────────────────────────────────

 SQL Drivers中的SQL link和ReportSmith为

SQL Drivers  Delphi数据库应用程序提供了访问远程SQL

 服务器的驱动程序,如访问ORACLE、SyBase、

 Microsoft SQL server、Informix、Intermix

─────────────────────────────────

Visual Query Builder 以可视化的方式建立SQL语句对数据库表和表

 中的记录进行操作

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

 

SQL links使得Delphi数据库应用程序利用SQL语言访问驻留在远程服务器上的数据,这些服务器包括ORACLE、Sybase、Microsoft SQL Server、Informix、InterBase。 当安装SQL Link驱动程序之后,SQL语句便可以直接操作服务器上的数据。

 

13.2.2 Delphi可以访问的数据源(DataSource)

 

Delphi数据库应用程序是通过BDE获取它们所需的数据的,BDE与不同类型的数据源打交道,BDE可以使用的数据源有如表13.3所示

 

表13.3 Delphi可访问的数据源

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

数据源(DataSource)   特 性 描 述  文件扩展名

─────────────────────────────────────

 数据库表是通过dBASE数据库管理系统或  

dBASE数据库  DBD建立的,每个表是一个独立的文件  .DBF

─────────────────────────────────────

 数据库表是通过Paradox数据库管理系统  .DB

Paradox数据库  或DBD建立的,每个表是一个独立的文件  

─────────────────────────────────────

ASCII文件  表是通过Database Desktop建立的,每个  .TXT

 表是一个独立的文件  

─────────────────────────────────────

本地InterBase服务器  数据库是通过InterBase数据库管理系统  .GDB

 建立的,多个表包含在一个数据库文件中  

─────────────────────────────────────

SQL数据库服务器:  数据库是通过相应的数据库服务器提供的 依赖不同的

ORACLE,Sybase,Informix 专用或通用工具建立的,也可以通过DBD来 数据库管理

Microsoft SQL Server  创建数据库,并通过SQL Link访问数据库  系统

InterBase    

─────────────────────────────────────

ODBC数据源  主要是指那些具有ODBC接口的数据库系统 依赖于相应

 如MS Access,Btrieve等  的数据库

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

13.3 Delphi数据库的体系结构 

        Delphi使用可视化的部件创建数据库应用,跟创建其它的非数据库应用程序一样,数据库部件都具备一定的属性,程序设计人员可以在设计过程中设置部件的多种属性,也可以在程序运行过程中通过程序来设置部件的各种属性。

        在Delphi部件板上有两页数据库部件用于开发数据库应用程序:

        数据访问部件页:该页上的部件主要用于说明有关的数据库的信息,如应用程序要访问(连接)的数据库,要访问数据库中的具体的数据库表,以及要访问表中哪些字段等,在实际的开发应用中常用的部件有TDataSource、TTable、TQuery等。

        数据控制部件页:该页上的部件主要用于显示浏览数据库中的数据信息,为用户提供了一个可视化的界面,常用的部件有:TDBGrid、TDBEdit、TDBCheck等,可以让用户对数据库中的信息进行有效的浏览、编辑、插入、删除等操作。

        TTable、TQuery、TStoredproc部件负责与实际的数据库表联系, 并从中获取数据信息,因而它们又常常被称为数据集部件,它们在程序设计过程中是可见的,但在程序运行时是不可见的, 它们通过 BDE 为应用程序提供与数据库的连接, 数据控制部件通过TDataSource部件与数据集部件相连,为用户提供一个可视化的界面, 并在其中显示数据库中的数据信息。

13.3.1 数据访问部件 

数据访问部件页上提供了一组数据访问部件用来访问数据库中的数据。  

图13.3 数据访问页上的数据访问部件 

当要创建一个数据库应用时,首先在窗体中选择一个数据访问部件,然后为数据访问部件设置有关的属性,说明要访问的数据库、数据表以及表中的记录等,数据访问部件为数据控制部件与数据源建立一条通道。数据访问部件在程序运行时是不可见的。下表列出了数据访问页上的数据访问部件以及它们的主要用途: 

表13.4 数据访问部件

━━━━━━━━━━━━━━━━━━━━━━━━━━━━

部件名称   主 要 用 途

────────────────────────────

 作为数据集部件TTable、TQuery、StoredProc组

TDataSource 件与数据浏览件TDBGrid、TDBEdit之间传送数据

 的通道。

────────────────────────────

 它是存取磁盘上数据库表的媒介,它通过BDE存

TTable  取数据库表中的数据,TTable再与TDataSource

 进行“对话”,使得数据浏览部件能够有效地从

 TTable中访问数据并能显示和编辑其中的数据。

────────────────────────────

 它利用SQL语言访问磁盘上数据库表中的数据,

TQuery  并与TDataSource“对话”,实现数据浏览部件

 对数据库的访问。

────────────────────────────

TStoredProc 在应用程序中,它主要用来访问远程服务器中的

 存贮过程

────────────────────────────

 当应用程序要登录到一个远程服务器上的数据库

TDatabase  时,可以用该部件来建立应用程序与数据库永久

 性的连接。

────────────────────────────

TBatchMove 用于复制数据库表的结构或表中的记录。

────────────────────────────

TReport  用于创建数据库的输出报表。

━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 

        值得指出的是在绝大多数数据库应用中,一般都是使用数据集部件TTable、TQuery或TStoredProc与磁盘上的数据库进行连接,用TDataSource部件连接数据控制部件和数据集部件,当然用户也可以自定义数据集部件,用于数据库应用当中, TTable 、 TQuery 和TStoredProc部件中都包含一个不可见的TField类型的对象Fields,Fields是一个串列表,它对应于数据库表或一个查询结果的列或字段。Fields对象是伴随着TTable、 TQuery 和TStoredproc部件的活动状态动态地建立的,当数据库表被关闭时,Fields 对象也随之消失,它在程序设计和程序运行过程中都是不可见的。

当然也可以利用Fields Editor建立永久性的Fields对象供Delphi应用程序使用, 我们将在后面的内容中详细阐述。

13.3.1.1 TTable部件 

         利用TTable部件程序设计人员甚至可以不需要编写任何程序便可对数据库进行访问,在一个应用程序窗体中放置一个TTable部件的过程如下:

1、在部件选择板上选择Data Access页;

2、单击Table图标;

3、在窗体内单击鼠标,获得一个TTable部件;

4、为TTable部件设置有关的属性:

DatabaseName属性指定要访问的数据库所在的路径名,路径名可以用别名来表示。

TableName属性指定要访问数据库中具体的数据库表。

Active属性设置为True时,表示打开要访问的数据库表;设置为False时,暂时 不打开要访问的数据库表。

        缺省情况下,TTtable部件中包含了要访问的数据库表中所有的字段和记录, 用鼠标双击TTable图标时,会出现一个字段编辑器(Fields Editor),使用Fields Editor可以对TTable部件中包含的数据库表中的字段的显示格式等属性进行编辑,具体可以控制:

● 建立一个永久性的字段列表,包括字段的顺序,字段的类型等,即使磁盘上实际的数据库表的表结构发生了改变,我们建立的这个永久性的字段列表也不会发生改变

● 为每个字段指定一个便于阅读和使用的名字

● 指定字段显示的顺序

● 为每个字段指定一个用于显示的字符串

● 为字段增加合法性检验

● 为了显示的需要还可以建立新的字段(如可计算的字段)具体的使用方法见后面的内容

13.3.1.2 TQuery部件 

        TQuery部件是我们使用SQL语言开发数据库应用程序的有力工具,因为使用SQL语言,我们可以非常方便灵活地对一个或多个数据库表中的记录进行访问,所以利用TQuery我们可以查询本地的数据库如Pà?aradox和dBASE数据库系统中的数据,我们还可以使用TQuery部件对一个远地的数据库SQL服务器进行访问,建立Client/Server模式的应用程序。

在一个应用程序窗体中放置一个TQuery部件的过程如下:

1、在部件选择板上选择Data Access页;

2、单击Query图标;

3、在窗体内单击鼠标,获得一个TQuery部件;

4、为TQuery部件设置有关的属性:

DatabaseName属性指定将要访问的数据库的路径名。

        SQL属性指定对数据库表进行访问SQL语句,它可以是一条查询语句也可以是一条 修改语句或插入语句等。在对象浏览器上,单击SQL属性时,会打开一个字符串编辑器供程序设计者输入SQL语句。

        在这里要注意在TQuery部件中,不是用TableName 属性来指定要访问的数据库中的数据库表,而是在SQL属性中,通过SQL语句来指定将要访问的数据库表。

13.3.1.3 TDataSouece部件 

        TDataSource部件是连接数据集部件TTable、TQuery、 TStoredProc 和数据控制部件TDBGrid、TDBEdit等的桥梁,TTable、TQuery、TStoredProc部件通过BDE可以实现与磁盘上的数据库连接即访问, 但它们本身不能显示数据库中的数据信息, 而数据控制部件如TDBGrid、TDBEdit等能够提供可视化的界面,显示数据库中的数据信息,但它们不具备访问磁盘数据库的能力,正是TDataSource将这两者有机地结合起来, 使得用户才能交互地对数据库中的数据信息进行查询、修改、插入、删除等操作。

在应用程序窗体中放置TDataSource部件的过程如下:

1、在部件选择板上选择Data Access页;

2、单击DataSource图标;

3、在窗体内单击鼠标,获得一个TDataSource部件;

4、为TDataSource部件设置有关的属性:

Dataset属性指定一个数据集部件,可以是TTable、TQuery或TStoredProc部件的 名字。 


想死你们了!

TOP

DELPHI基础教程

第十三章 Delphi开发数据库应用程序概述(二)


13.3.2 数据控制部件 

        数据控制部件页上的部件,主要用于设计用户界面,对数据库中的数据进行浏览、编辑、插入、删除等操作。因而数据控制部件常常又被称为数据浏览部件,数据控制部件其实是在Standard页上的标准部件的基础上,相应地增加了数据浏览功能,使得它们能够显示和编辑数据库中数据信息。 

        数据控制部件既能够把数据库中的数据显示到窗体中,又可以将其自身的经过修改的数据写回到数据库中。下表列出了数据控制页上的数据控制部件及它们的主要用途。 

表13.5 数据控制部件

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

部件名称   主 要 用 途

───────────────────────────────

 使用该部件可以向前向后移动记录指针,可以使

TDBNavigator  用该部件对单条记录进行编辑,还可以用它来插

 入、删除记录以及刷新显示和取消前一次的操作

───────────────────────────────

 它是显示数据库中的数据的文本框,它只能显示

TDBText  数据库表当前记录的字段值,用户不能对其中的

 数据进行修改。

───────────────────────────────

 它是显示和编辑数据库表中的数据的编辑框,它

TDBEdit  既可以显示又可以编辑数据库表中当前记录的字

 段值。

───────────────────────────────

TDBCheckBox  它是浏览数据库中的数据的检查框,它可以用来

 显示和编辑数据库中的布尔型字段的字段值。

───────────────────────────────

TDBListBox  它是浏览数据库中的数据的列表框,它可以用一

 个列表框来显示数据库表中一个字段的值。

───────────────────────────────

TDBComboBox  它是浏览数据库中的数据的组合框,它可以用一

 个组合框来显示数据库表中一个字段的值。

───────────────────────────────

TDBRadioGroup  它是浏览数据库表中的数据的单选钮,用一组单

 选钮可以确定显示数据库表中哪一个字段。

───────────────────────────────

TDBGrid  它是浏览数据库中的数据的网格,以网格的方式

 显示数据库中的数据,在网格中还可以对数据库

 中的数据进行编辑。利用Fields Editor可以对

 数据库表中字段的显示格式、显示顺序、是否显

 示等进行控制。

───────────────────────────────

TDBMemo  它主要用于浏览数据库中备注型的字段,它可以

 用来显示数据库表中当前记录中的BLOB型字段。

───────────────────────────────

TDBImage  它是浏览数据库中的数据的图像框,它可以用于

 显示、拷贝、粘贴据库表中图像类型的字段。

───────────────────────────────

TDBLookUpList  它是浏览数据库表中的数据的列表框,在基于一

 个数据库表的应用中,用它可以显示另一个数据

 库表中一个指定的字段值。

───────────────────────────────

TDBLookUpCombo 它是浏览数据库表中的数据的组合框,在基于一

 个数据库表的应用中,用它可以显示另一个数据

 库表中一个指定的字段值。

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

        数据控制部件为开发Delphi数据库应用程序提供可视化的用户界面,不管应用程序是访问本地数据库中的数据文件,还是访问远程数据库服务器中的数据文件,用户界面都是一致的,即数据库的物理位置对数据控制部件是透明的。

13.3.3 数据库窗体专家和数据库操作台(DBD) 

        Delphi为用户开发简单的数据库应用程序提供了一个开发工具叫做“数据库窗体专家”(Database Form Expert),在Delphi系统菜单Tool菜单下可以找到。

        数据库窗体专家能够自动生成简单的数据库应用程序中所必须完成的许多任务,它还可以生成基于单个数据库表的应用程序窗体或基于主要──明细型多个数据库表的应用程序窗体,数据库窗体专家能够自动完成的任务如下:

● 放置数据库部件到窗体中(TDataSource部件)

● 为数据集部件(TTable、TQuery)和磁盘上的数据库建立连接

● 建立数据源(TDataSource)与数据控制部件的连接,数据源(TDataSource) 与 数据访问部件(TTable、TQuery)的连接

● 为TQuery部件编写SQL语句

● 为窗体中的部件定义Tab顺序

        数据库操作台(DBD)是数据库维护和数据定义工具,程序设计人员利用它可以查询、连接、建立、重构、索引、修改和拷贝数据库表,包括Pà


想死你们了!

TOP

DELPHI基础教程

第十四章 简单数据库应用的创建及MASTAPP介绍(一)

        Delphi中嵌入的数据库应用开发工具如Database Form Expert具有很强大的功能,我们不需要编写任何程序代码便可以快速地创建一个简单的数据库应用程序,甚至还能创建基于多个数据库表的主要──明细型数据库应用程序。

        本章主要介绍用Delphi开发简单的数据库应用程序的一般方法和步骤,首先让读者对Delphi强劲的数据库应用开发工具有一个直观的印象,然后在此基础上进行复杂的数据库应用程序的设计,本章主要包括以下内容:

● 创建数据库应用窗体

         包括用Database Form Expert 或手工方式创建简单的无需编写程序代码的应用程序或者利用多个部件并编写功能复杂的程序代码创建主要──明细型数据库应用程序。

● 在应用程序中控制字段有关的属性

         描述怎样读写数据库表中字段的值和控制字段的显示格式等。

 

         本章所介绍的例子中用到的窗体、数据库表以及相关的文件都是在安装Delphi时缺省安装在C:\DELPHI\DEMOS\DB\MASTAPP目录中,并且用别名DBDEMOS表示这一子目录。 在本章例子中,除特殊声明外,所有的TTable和 TQuery 部件的 DatabaseName 属性都设置为DBDEMOS。

14.1 简单的基于单表的据库应用 

         用Decphi创建显示一个数据库表中的内容的应用非常简单和方便,只需要三个部件,只要将这三个部件通过相关的属性相互联系起来,不需要编写任何程序代码便可以实现。例如,用户想查看数据库表Customer.DB中的内容时,可以按下面步骤来实现: 

14.1.1 选择相关的部件: 

         选择菜单Project/New开始一个新工程,并修改Form1的Caption属性为CustomerFrom1并把Name属性设置为CustomerForm1,然后从部件选择板上的Data Access 页上选取一个Datasounce部件和一个Table部件放到窗体的左上角,它们是非可见的部件, 在窗体中我们看到的只是部件的图标;从Data Control页上选取DBGrid部件放到窗体中前两个部件的下面。完成这些工作之后,窗体如图14.1所示。  

图在CustomerFrom1窗体中放置三个部件 

14.1.2 设置部件的属性 

为了使TDBGrid部件能够显示数据库表Customer.DB中的客户信息,我们必须修改窗体三个部件相关的属性,这些属性的设置如表14.1所示。 

表14.1 CustomerFrom1窗体中三个部件的属性设置

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

属 性 属 性 值

──────────────────────────────

DataSource1.AutoEdit False

DataSource1.DataSet Table1

Table1.DatabaseName DBDEMOS

Table1.TableName CUSTOMER.DB

Table1.Active True

DBGrid1.DataSource DataSource1

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 

        这里要注意的是:DBDEMOS是Delphi缺省安装时C:\Delphi\DEMO\DB\MASTAPP目录的别名,而且数据库表Customer.DB存在该目录下,用户在使用这一例子时, 请注意这两项设置都是正确的。另外 Datasource1.Dataset,Table1.TableName和DBGrid1.Datasource属性都有下拉式列表框允许用户从可能的值列表中选择它们的值,这样能方便我们进行属性的设置,而且不容易出错。

        Datasouuce1.AutoEdit属性设置为False是为了防止用户修改数据库表中的数据, 在下面的讨论中我们将详细地进行说明。

        Table1.Active设置为True时,Delphi会打开Table1.TableName所指定的数据库表。如果这个数据库表不存在(或表中什么也没有, 即空表), Delphi 会弹出出错信息并且Table1.Active变成False。当Table1.Active被设置成True之后,Table1 部件的一些属性就不能再修改了,如Table1.DatabaseName和Table1.Tablename属性。若要修改它们, 必须首先要将Table1.Active属性设置为False,然后再进行修改,否则,Delphi会弹出错误信息“Cannot perform this operation on an open database”。当看到这个错误信息时,只需把Table1.Active置成False,完成相关的修改后,再把 Table1. Active 属性设置为True。

        当我们把DBGrid1.DataSource的值设置成DataSource1时,Delphi会把Customer.DB中的数据填充到DBGrid1部件中,并且可以用DBGrid1中的滚动条来浏览数据库表中的所有记录。 

14.1.3 运行程序 

        保存文件,命名代码单元为Cust.pas,命名工程名为CustPRJ.DPR,然后按F9编译并运行程序。程序执行之后,我们可以使用滚动条或键盘移动键在字段和记录间移动。但不能修改表中的数据,因为Datasouc1.AutoEdit1属性已被设置为False。

        Cust程序中的三个部件都有各自的特殊用途,三个部件的相关属性在内部相互联系生成最终的应用程序。TTable部件连接磁盘上的实际数据库表和应用程序中其他部件的通道。TTable部件具有打开和关闭、读取、更新以及其他处理磁盘数据库文件的方法。

        TDatasource部件是连接TTable部件和数据浏览部件如TDBGrid部件的桥梁。 TDBGrid部件用于显示数据库表中的数据信息,它为应用程序提供一个直观的界面。图14.2阐述了这三个部件之间的关系。 

Cust程序中三个部件之间的内部关系 

        TDBGrid 部件的奇妙之处在于它知道如何去获取数据库表中的下一条或前一条记录,我们使用滚动条或箭头键便可以完成这项任务。TDBGrid部件不知道如何增加、 删除和修改记录。如果想让 Cust 程序能够修改数据库表中的记录, 只要把 Datasource1 部件的AutoEdit属性设置成True , 并重新编译和运行程序就可以达到目的。 使用箭头键, 把DBGrid的高亮度条定位到某一个字段上,然后键入新值,该字段中的值将被键入的新值所取代,并且当移动到另一条记录时,健入的信息会自动写入数据库表中。如果想放弃所做的改动,只需在离开该字段前按一下Escape键。

        如果想在表中增加新记录,可以把高亮度条移到网格底端的空白记录上并输入新记录的有关字段值。也可以在用户指定的某一条记录的后面插入一条新记录,只要把高亮度条定位到指定的记录上,按Ins键,使可以在该记录的后面插入新记录。

        删除某一条记录时,把高亮度条定位在想删除的记录的任何字段上,按Ctrl+ del键,这时会出现保护信息,我们可以确认是否真的想删除该项记录。

        TDBGrid为用户提供了较完备的功能,用于控制是否编辑、增加或删除记录。 若想禁止对数据库表作任何修改,设置TDBGrid部件的Readonly属性为 True , 并设置 Option.dgEDiting为False(这将为我们提供一个只读的数据库表浏览器而不是数据库编辑器,但它隐含着增加、编辑和删除记录的能力)。TDBGrid部件的这些属性和Option属性其它选项的各种不同组合可以让我们很方便地对数据库表进行有效的浏览、编辑等操作。

        如果我们经常使用像电子表格那样的界面来显示和编辑数据记录,TDBGrid 部件便是一个很方便的工具,但那并不是最友好的用户界面,如果想拥有更优美更直观的界面,我们还可以使用单独的数据浏览部件来显示数据库表中各个字段的值,并利用TDBNavigator部件控制对数据库表的存取。 

14.2 利用TDBNavigator部件创建存取程序 

          我们可以改进一下Cust程序以便它一次只在对话框中显示一个客户的记录信息,并用一个TDBNavigator部件控制对记录存取──允许我们选择一个记录来显示或编辑以及增加和删除记录。完成的应用窗体。

增强的Cust程序

14.2.1 创建应用程序窗体 

        我们可以非常迅速地创建起来,因为到目前为止我们对创建窗体的方法已经比较熟悉,我们首先把所有的部件都放到窗体中,然后再设置它们的属性。

        开始一个新工程,设置窗体Form1的Name 属性为 Customerform2 , Caption 属性为         CustomerForm2。然后从部件选择板上的Data Access页上选取一个Datasource部件和一个Table部件放在窗体的右上角。再从Data Controls页上选取DBNatvigator部件放在窗体的左上角。

        窗体中其余的部件如图14.3所示。它们是TDBEdit和TLabel部件,按图14.3 所示创建并布置部件,分别命名DBEdit部件为EditCustno、 Editcompany 、 EditAddr1 、EditAddr2、EditCity、EditState、EditZip、EditCountry、EditPhone 、EditFAX、EditTaxRate、EditContact。

        现在我们来连接TTable部件和 TDataSource 部件, 然后连接所有的数据浏览部件和DataSource部件。设置TBNavigator部件和TDBEdit部件的属性,它们的DataSource属性都设置为DataSouce1。我们最后要做的事是连接窗体中各个TDBEdit 部件和它们在数据库表中对应的字段名,通过设置TDBEdit 部件的 DataField 属性来完成。 例如要连接命名为EditCustNo的TDBEdit部件和数据库表中的CustNo字段,具体步骤如下:

①选中窗体中的EditCustNo部件。

②在Object Inspector窗体中,单击DataField属性右边的箭头。

③从下拉列表中选中CustNo字段名。

          对窗体中的其他TDBEdit部件执行以上操作连接到其对应的字段,然后保存文件。 命名代码单元名为Cust2.pas,命名工程名为Cusprj2.DPR。 

14.2.2 使用TDBNavigator部件移动记录指针 

        上述程序运行之后,在数据浏览部件中会显示数据库表中的第一条记录。利用Tab 键可以在字段之间移动,但是不能编辑字段。因为我们为了防止意外修改,设置了Table1的AutoEdit属性值为False。如果想对数据库表中的记录进行编辑、 插入和删除操作或者想显示数据库表中另一条记录, 需要按 TDBNvigator 部件上这些功能所对应的功能按钮。TDBNavigator部件上的按钮和它们的功能如图14.4所示。

TDBNavigator中的按钮 

        TDBNavigator部件的绝大多数功能都可以根据其按钮的图标能够很容易地识别出来,而且TDBNavigator部件本身能感知到很多事情,如当前指针是否在数据库表的开头或尾部。如果用户正在查看数据库表中的最后一个记录,Next和Last按钮将会变灰成为非活动状态。同样, 如果用户当前正在浏览数据库表中的第一条记录, TDBNavigator 上的 First 和Previous按钮会变灰而成为非活动状态。有关各个按钮的作用的更详细说明请查看联机帮助。

        如果用户想修改当前的记录,单击TDBNavigator部件的Edit按钮,然后完成需要做的修改,在做完修改之后,单击Post按钮以便将作的修改写入实际的数据库表中(更新实际的数据库表中的记录在数据库术语中叫作“投寄”记录即PostT)。 如果想取消所做的修改,单击Cancel按钮。Cancel按钮只取消自从上一次往数据库表中投寄记录以来对记录所做的修改。例如,如果用户曾修改了CustNo字段并单击了Post按钮投寄了修改,然后再修改Company字段并按Cancel,那么只有对Company所做的修改将会被取消。也就是说,一旦修改被写入了数据库表中,再按Cancle按钮是无法取消对记录的修改的,要想恢复到以前的状态,用户必须要重新编辑修改记录。值得注意的是,当用户修改了当前的记录,并移动到其他记录时,TDBNavigaator部件会自动地投寄用户对记录的修改。 例如:如果我们修改了记录的Company字段,并没有按Post按钮以更新表中的记录, 而是移动到下一条记录,这时用户对记录的修改也会自动地被写入数据库表中。 

14.2.3 定制TDBNavigator部件 

        TDBNavigator部件中的按钮对我们开发人员来说是很方便的,但对于程序的最终用户来说不一定那么一目了然。为了帮助最终用户或初级用户更方便有效地使用TDBNavigator部件,我们可以设置TDBNavigator部件的ShowHint属性为True,这样当鼠标光标停留在TDBNavigator部件上的某一个按钮上超过大约1秒钟,在屏幕上便会出现该按钮的提示信息。如果我们不想使用TDBNavigator部件本身嵌入的提示信息,我们还可以设置TDBNavigtor部件的Hints属性,为每个按钮指定特定的提示信息,以帮助用户使用TDBNavigator部件。

        TDBNavigator部件中有多个功能按钮,但并不是所有的按钮对每一个数据库应用程序都是需要的,特别是那些不允许修改表中的数据,或修改只是在很严格的控制下进行的数据库应用程序。我们可以通过设置TDBNavigator部件的 VisibleButtons 属性来确定要在TDBNavigator中显示哪些按钮步显示哪些按钮。例如,如果我们不允许用户修改表中的记录,我们就不需要Add、Delete、Post、Cancel 或 Refresh 按钮, 我们设置这些按钮的VisibleButtons属性为False,这样在TDBNavigator部件中将不会出现这些按钮。

        TDBNavigator部件的ConfirmDelete属性和Delete 按钮配合使用对用户删除数据库表中的记录是非常有用的,当ConfirmDelete属性设置为 True (缺省设置), 当用户单击Delete按钮试图删除当前记录时,Delphi会弹出一个确认框,要用户确认是否真的想删除当前记录。这样,在用户进行删除记录的操作时,会更安全一些。如果用户不希望在按下Delete按钮时出现确认框,只要把ConfirmDelete设置为False就可以了。

        还有一些属性可以用来定制TDBNavigator部件的外观和性能,有关这方面的详细信息请参看联机帮助。

14.3 创建主要──明细数据库应用 

        我们前面在介绍的基于单个数据库表的数据库应用程序只能对数据库表进行简单的管理,大多数只用来浏览单个数据库表中的记录信息,如果我们想浏览多个相关的数据库表中的记录信息,就必须要创建主要──明细型数据库应用程序。

        在主要──明细型数据库应用程序中,一个数据库表作为主要表,其中存放着综合信息,其他的数据库表和主要数据库表相关联,它们当中存放着更详细的信息。例如,当数据库表Customer.DB作为主表,它包含着客户的综合信息如编号、姓名、 所在公司的名称等等。而数据库表Orders.DB中包含着每个客户的订货单的详细信息,如订单号、 发货日期、起运日期、发货目的地等信息,这样当在Customer.DB表中查看某一位客户时, 利用其中的字段CustNo与Orders.DB表发生联系,自动地从Orders.DB表中检索出这位客户曾经发来的所有订货单的详细信息。主要──明细型数据库体现了关系数据库的特点,即独立的数据库表之间基于它们共同的字段而发生联系。在这里Customer.DB和Orders.DB拥有一个共同的字段CustNo。

14.3.1 一对多关系的主要──明细型数据库应用程序 

        主要和明细数据库表之间存在一对多的关系,意思是说对于主表中的一条记录,在明细表中有多条记录与之对应。例如,创建一个主要──明细型数据库应用程序,其包括两个表Customer.DB和Orders.DB,它们分别作为主表和明细表,创建好的应用如图14.5所示,窗体中各部件的属性设置  

表14.2 主要──明细型数据库应用中各部件的属性

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

部 件 属 性 属 性 值 注 释

──────────────────────────────────

Table1 Active True

(主表) DatabaseName DBDEMOS

TableName CUSTOMER.DB

──────────────────────────────────

DataSource1 DataSet Table1

AutoEdit False

──────────────────────────────────

Table2 Active True

(明细表) DatabaseName DBDEMOS

TableName ORDERS.DB

IndexFieldNames CUSTNO 指定字段CUSTNO作为

Table2中的索引字段

MasterField CUSTNO 指定与主表发生联系

的字段

MasterSource DataSource1 说明与主表相连接的

数据源即DataSource

──────────────────────────────────

DataSource2 DataSet Table2

AutoEdit False

──────────────────────────────────

DBGrid1 DataSource DataSource1

(对应主表)

──────────────────────────────────

DBGrid2 DataSource DataSource2

(对应明细表)

TableName ORDERS.DB

──────────────────────────────────

DBNavigator1 DataSource DataSource1

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 

        一对多关系是非常普遍的关系。即便是简单的名字/ 地址数据库都有一对多的关系,因为一个人可能不止一个地址:家庭地址、工作地址、还可能有别墅地址。在本例中,公司的一个客户常常有多个订货单,当我们单击DBNavigator1中的向前、向后按钮时,会移动DBGrid1中的记录指针,而在DBGrid2中会自动显示与DBGridl 中当前记录相关的多条记录,即显示一个客户的信息时,同时会显示该客户的所有订货单的详细信息。 

14.3.2 一对多──多关系的数据库应用 

        前面我们介绍了基于两个表的一对多关系的应用,下面我们介绍怎样创建一个从三个表中浏览数据记录的一对多关系的应用。

         例如:一个客户也许有多张订货单,而每一张订货单中有多个订货项目,这样我们在Customer.DB表和Orders.DB表之间建立一个主要──明细型关系,同时在orders.DB 表和Items.DB表之间建立一个主要──明细型关系。 

窗体中各部件的属性如表14.3所示 

表14.3 一对多──多关系的应用中各部件的属性

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

部 件 属 性 属 性 值 注 释

──────────────────────────────────

Active True

Table1 DatabaseName DBDEMOS

TableName CUSTOMER.DB

──────────────────────────────────

DataSource1 DataSet Table1

AutoEdit False

──────────────────────────────────

Active True

DatabaseName DBDEMOS

Table2 TableName ORDERS.DB

IndexFieldNames CUSTNO

MasterField CUSTNO

MasterSource DataSource1

──────────────────────────────────

DataSource2 DataSet Table2

AutoEdit False

──────────────────────────────────

Active True

DatabaseName DBDEMOS

Table3 TableName ORDERS.DB

IndexFieldNames ORDERNO

MasterField ORDERNO

MasterSource DataSource2

──────────────────────────────────

DataSource3 DataSet Table3

AutoEdit False

──────────────────────────────────

DBGrid1 DataSource DataSource3

──────────────────────────────────

DBNavigator1 DataSource DataSource1

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 

        窗体中其余的部件都是TDBEdit和TLabel部件,它们用于显示Customer.DB中的字段值和Order.DB中的字段值。在该例子中,总共连接了三个表, Customer. DB 表是主要表,Orders.DB表在窗体中起到了双重作用,它既是Customer.Db表的明细表,同时又是Items.DB表的主要表,Items.DB表是Orders.DB表的明细表。 

14.4 字段对象的使用 

          Ttable和TQuery部件中有一个TField类型的属性Fiedls,Fields是TField类型的对象的列表,TField对象列表是Delphi数据库中较难以理解的一个对象,它是 TTable 部件和TQuary部件的一部分,它们是不能够选择到窗体中的独立的部件,而且无论是在设计阶段还是在程序运行过程中,它们都没有可见的图像。即使到Object Inspector窗中察看它们也很困难。

        Tfield对象是在打开磁盘上的数据库表时动态产生的,并在数据库表被关闭时自动消失,TField对象可以控制表中的每一列是否在数据浏览部件中显示以及以何种格式显示等等。通过字段编辑器(Fields Editor)我们可以建立永久性的TField 对象列表代替动态的Tfield对象列表供Delphi应用程序使用,通过Fields Editor建立的永久性的字段对象会自动地加入到程序库单元的TForm类型定义中 ,它们保存在应用程序中,即使数据库表的基本结构发生了改变,它也是一直保留着,当然如果修改后的表中使得原来所定义的字段对象不再存在,Delphi应用程序在运行过程中会给出现错误信息。 

14.4.1 字段对象的类型 

字段对象TField对应数据库记录中的各个字段,因为数据库记录中的字段有多种数据类型,因此对记录字段可能出现的每一种数据类型都有一个独立的TField类型与之对应。TField的类型如表14.4所示 

表14.4 字段对象的类型

━━━━━━━━━━━━━━━━━━━━━━━━━

字段对象的类型 对应的数据类型

─────────────────────────

TBooleanField 布尔型数据

TCurrentyField 货币型数据

TStringField 字符串数据

TIntegerField 整数型数据

TBLOB 大二进制对象

━━━━━━━━━━━━━━━━━━━━━━━━━ 

         在大多数情况下可能使用的是TStringField和TIntegerField类型的字段对象, 从编程的角度来看这些TField对象的不同类型是完全相同的,应用程序是根本不必关心TField对象的实际类型,它们之间的主要区别在于:它们内部保留的以及它们和数据库表之间传递的数据类型不一样。

14.4.2 创建永久性的字段对象 

        我们知道字段对象在设计和运行阶段都是不可见的,它既可以随着磁盘上的数据库文件被打开时动态地生成也可以通过字段编辑器Fields Editor来创建它。 在应用程序中使用Fields Editor可以为数据库表中的字段创建相应的永久性的TField对象,TField 部件是不可见的部件,但是通过它,我们可以定义数据库表中各字段的显示属性和显示顺序以及控制字段的取值范围等。下面的例子,告诉我们如何使用Fields Editor定义Customer.DB表中的四字段,并在网格中显示表中的记录信息。

操作步骤:

1、建立一个基于 customer. DB 表的数据库应用窗体, 并在窗体中用一个网格显示customer.DB中的全部字段,详细方法请参见14.1节,建好的窗体如图14.1所示。

2、设置窗体中Table1的Active属性为True,使网格显示表中的记录。

3、选中Table1并双击鼠标左键,打开字段编辑器Fields Editor, 缺省情况下字段列表为空。

4、单击鼠标右键弹出一个弹出式菜单,然后选择Add Fields菜单项,缺省情况下表Customer.DB中的全部字段被选进字段列表框。 从字段列表框中选择你要在网格中显示的字段,具体做法是:单击Custno字段,并按住CTR键,再单击Company、Phone、LastInviceDate字段,然后单击OK按钮,确认被选择的四个字段,时窗体中的DBGrid1中只显示刚才被选中的四个字段值,而不再显示表中其它的字段值。

 


想死你们了!

TOP

DELPHI基础教程

第十四章 简单数据库应用的创建及MASTAPP介绍(二)
5、改变字段的显示顺序。单击LastInvoiceDate 字段并将它拖放到字段列表框中的第三行,即处于Company和Phone字段之间。此时窗体中显示Customer.DB 表中记录的字段将按新的顺序显示。

6、选择Close按钮,关闭字段编辑器Fields Editor。

7、按F9,运行上述程序。

14.4.2 字段对象的属性设置 

     虽然字段对象是不可见的对象,但是它同样具有很多的属性。在程序设计阶段,我们通过一定的方式可以设置它的有关属性,下面是设置字段对象的属性的方法和步骤。

1、选择窗体中的table1。

2、双击table1,打开字段编辑器Fields Editor。

3、选择要设置属性的字段。

4、在Object Inspector中修改字段对象的属性。

     我们可以按上述方法设置Table1中各字段对象的有关属性,当我们选择Custno字段并修改其属性,窗体内会出现对话

字段对象的属性

     修改字段CustNo的Alignment属性为taCenter,此时网格中显示的CustNo 字段值由原来的右对齐变成了居中。

表14.5中列出了字段对象在设计阶段可以修改的属性以及属性说明 

表14.5 字段对象的重要属性

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

属 性 注 释

─────────────────────────────────

Alignment 说明字段值的显示方式:左对齐、右对齐、居中

─────────────────────────────────

Calculated 当该属性值为True时,表明该字段的值是根据其它字

段的值计算得来的。否则该字段是数据库表中的字段

─────────────────────────────────

DisplayLabel 说明字段在网格部件中显示时的标题,缺省情况下字

段的标题就是字段名

─────────────────────────────────

DisplayWidth 说明字段在网格中显示时所点的列宽度,即字符数

─────────────────────────────────

DisplayFormat 说明字段在显示和编辑状态下的显示格式和输入的过

and EditMask 滤条件(限定用户输入字段值的范围)。

─────────────────────────────────

FieldName 在数据库表中对应于该字段对象的字段名称

─────────────────────────────────

Index 指定该字段对象在数据集部件中的逻辑位置,如Table1

中的第一个字段对象的Index值为0

─────────────────────────────────

Name 字段对象的名称,缺省情况下,它是TTable、TQuery

部件的名称加上字段的名称。如上例中的CUSTNO字段

对象的Name属性值为Table1CUSTNO,通过字段对象的

Name属性可以访问该字段的值,如Table1CUSTNO.Value

─────────────────────────────────

ReadOnly 说明该字段是否能被修改,当该属性值为True时,该

字段的不能被修改

─────────────────────────────────

Visible 当该属性值为True时,在与之相连的网格部件中将不

显示该字段

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

      根据表14.5中的属性, 我们可以修改上例中一些字段的某些属性, 使网络中显示表Customer.DB中的记录更符合我们的工作习惯。修改的属性如表14.6所示, 经过修改后的程序运行结果如图14.10所示。

表14.6 修改后的字段对象的属性

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

字 段 属 性 属 性 值

─────────────────────────────

CustNo DisplayLabel 客户编号

─────────────────────────────

Company DisplayLabel 公司名称

─────────────────────────────

Phone DisplayLabel 电话号码

─────────────────────────────

LastInvoiceDate DisplayLabel 购买日期

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

修改字段对象的属性  

14.4.4 字段对象的访问  

     字段对象在应用程序中有动态生成的,也有通过字段编辑器Fields Editor 创建的永久性的,它们虽然在设计和运行阶段都是不可见,但是它们跟其他的对象一样都拥有自己的属性、方法和事件,因此我们在应用程序中是可以对字段对象进行控制和访问的。

因为动态字段对象是没有自己的名字的,永久性的字段对象有自己的名字,所以对这两种字段对象的访问方法是不一样的。

14.4.4.1 动态字段对象的访问

     动态字段对象存在于数据集部件TTable和TQuery部件中,它们是随着磁盘上的数据库文件的打开而动态生成的,并且每一个字段对象对应于数据库表中的一个字段(即记录的一列),TTable或TQrery部件中所有的字段对象存在属性Fields列表中,Fields列表中的字段对象就像数组元素一样拥有自己的索引号,我们可以用这个索引号来访问字段对象。索引号在程序运行时赋值,从0开始,表中最左边的一列(第一个字段)的索引号为0,紧接着右边一个为1,以此类推。访问这些属性的方法和处理其他对象一样。 

Table1.Fields[0].DisplayLabel:='标识符' 

     上述代码让我们访问与Table1相连的数据库表中的第一个字段,并为该字段指定一个标题,这是通过设置它的DisplayLabel属性值为一个特定的标识符来实现的。

    通过索引号来访问Fields属性中的字段在使用For循环对列号进行迭代时会非常有用。但是在大多数简单应用程序中,通过列名(字段名)来访问字段会更加明白而且易读。在TTable部件中,提供了一个名为FieldByName的方法以便让我们通过列名访问字段对象。

Table1.FieldByName('CustNo').DisplayLabel:='标识符'

通过这种途径同样可以访问CUSTOMER.DB表中的CustNo字段, 并为该字段指定一个标题信息。

    现在我们可以建立一个允许用户通过字段名和索引号来访问Customer.DB 表中的字段对象的简单窗体。

字段对象的访问

    在该应用窗体的运行过程中,我们通过程序来访问其中的字段对象并设置有关的属性,这一控制过程我们放在窗体的OnCreate事件处理过程中。

例14.1 在窗体的Oncreate事件处理过程中访问字段对象。 

procedure TForm1.FormCreate(Sender:TObject);

Begin

with Table1 Do

begin

{通过索引号访问字段对象}

Field[0].DisplayLabel:='客户编号';

{通过字段名访问字段对象}

FieldByName('Company').DisplayLabel:='公司名称';

FieldByName('Phone').DisplayLabel:='电话号码';

FieldByName('LastInvoiceDate').DisplayLabel:='购买日期';

end;

end;  

在程序运行过程中访问字段对象 

14.4.4.2 永久性字段对象的访问 

     通过字段编辑器Fields Editor 建立的永久性字段对象的访问相对于动态字段对象的访问要简单得多,我们在程序中可以直接通过字段对象的名称(即Name属性)进行访问。

例如:

Table1CustNo.DisplayLabel:='客户编号';

Table1CustNo.DisplayWidth:=12;

14.4.4.3 字段对象的读取和赋值 

    通过字段对象的Value属性,我们可以读取字段对象的值,例如在如图14.13所示的窗体中,单击Read按钮便可以将Customer.DB表中当前记录的COMPANY字段的值读取到编辑框Edit1中。 

读取字段对象的字段值

窗体中各部件的属性如表14.7所示 

表14.7 各部件的属性

━━━━━━━━━━━━━━━━━━━━━━━━

部件的属性 属 性 值

────────────────────────

Button1.Caption &Read

Button1.Name Button1

Label1.Caption 字段值

Label1.Name Label1

Edit1.Text

Edit1.Name Edit1

━━━━━━━━━━━━━━━━━━━━━━━━

 

其它部件的的属性跟前面的例子一样。

为Read按钮编辑的OnClick事件处理过程如下: 

procedure Form1.TButton1Click(Sender:TObject);

begin

Edit1.Text:=Table1Company.Value;

end;

    在这里要注意的是:从字段对象中读取字段值时必须要将它赋给与之数据类型相匹配的变量,否则会出错。在上面的程序代码中,Table1Company的类型是TStringField 即是字符串类型的字段,而编辑框Edit1的属性Text的类型也是字符串型的, 因而它们是匹配的。如果类型不匹配,则要经过一定的转换才能够相互赋值。如: 

Edit1.Text:=Table1CustNo.Value 

    这条代码在运行过程中将会出错,因为TablelcustNo是TFloatField 类型即是数值型数据,要在编辑框Edit1中显示数值型数据要经过下列转换: 

Edit1.text:=Table1CustNo.AsString; 

        AsString是字段对象的属性,通过字段对象的AsString属性可以读取字段值并且将它转换成字符串类型。字段对象的字段值可以转换成以下几种类型的数据:

AsString: 将字段值转换成字符串数据

AsBoolean: 将字段值转换成布尔型数据

AsDateTime: 将字段值转换成日期时间数据

AsFloat: 将字段值转换成数值型数据

AsInteger: 将字段值转换成整数型数据

下面的程序代码是从字段对象中读取字段值并将它显示在编辑框Edit1中, 或者将字段值赋给相匹配的变量。 

CustNoDouble: Double;

CustNoInt: Integer;

CustNoString: String;

{在Edit1中显示字段值}

Edit1.Text:=Table1Company;{类型相匹配,不需要转换}

Edit1.Text:=Table1CustNo.AsString;{类型不匹配,需要转换}

{将字段值赋给变量}

CustNoDouble:=Table1CustNo.Value;{类型相匹配,不需要转换}

CustNoInt:=Table1CustNo.AsInteger;{类型不匹配,需要转换}

CustNoString:=Table1CustNo.AsString;{类型不匹配,需要转换}

14.4.5 设定字段对象的显示格式 

    我们即可以在设计阶段设定字段对象的显示格式,也可以在运行过程中通过程序代码来设定字段对象的显示格式。

    例14.2 在如图14.10所示的窗体中,再增加一个TaxRate字段, 并在程序设计过程中设定它的显示格式为0.00%,即设置TaxRate字段对象的DisplayFormat属性为0.00% , 若TaxRate的值为0.085那么在网格部件中其显示的格式为8.50%。

    在运行过程中我们通过程序代码来设定字段Phone的显示格式, 美国的电话表示形式与中国的表示形式不一样(如美国808-555-0269,中国(808) 5550269 ), 为此我们将phone 字段的值表示成中国式的形式。 具体方法是:在 Object Inspector 中选取Table1phone对象,并为此对象的OnGetText事件编写如下程序代码:

TForm1.Table1PhoneGetText(Sender:TField;

Text:OpenString;DisplayText:Boolean);

begin

If DisplayText then

begin

Text:=Table1Phone.Value;

Delete(Text,4,1);

Delete(Text,7,1);

Insert('(',Text,1);

Insert(')',Text,1);

end;

end;

图14.14 设定字段对象的显示格式

14.4.6 自定义字段以及计算字段对象的创建 

    有时候为了使应用程序完成所期望的工作,我们要在数据库表现有字段的基础上增加一些自定义的字段,这些字段并不是数据库表中实际存在的字段,它们常常是根据数据库表中的其它的字段动态地计算出来的,因而它们常常被称为计算字段。

例如我们创建一个浏览ORDERS.DB表中记录的应用如图14.15所示。

浏览ORDERS.DB表中的记录  

首先,我们想在显示OREDRES.DB表的网格中增加一个自定义的字段对象,完成以下步骤:

1、双击窗体中的Table1,打开字段编辑器Fields Editor。

2、在Fields Editor窗口中,单击鼠标右键,选择New Fields菜单项。

3、Delphi显示New Fields对话框。选择Field Type列表框中的Currency 项, 并在Field Name文体框中输入Balance , 这样我们自定义了一个 CurrencyField 类型的字段Balance。Delphi会自动地填入相应的字段对象名,其缺省值为Table1Balance。如图14.16所示。 

图14.16 New Field 对话框  

4、单击Ok按钮,关闭New Field对话框。当Fields Editor 窗口重新出现时, 注意Balance已经出现在Fields列表框中。

5、在Fields Editor 窗口中单击鼠标右键, 并选择 Add Fields 菜单项, 打开AddFields对话框。

6、从Available Fields 列表框中, 按住 Ctrl 键并单击鼠标左键, 选择字段:

OrderNo、CustNo、SaleDate、ShipData、ItemsTotal、Amountpaid以及Balance.

7、单击OK按钮,关闭Add Fields对话框,得到如图14.17所示的Fields Editor窗口。 

图14.17 字段编辑器Fields Editor

8、双击Fields Editor的控制盒关闭字段编辑器Fields Editor。

    至此我们已经为Table1创建了一个自定义的字段对象Balance,下面我们把Balance字段设置成计算字段对象,使其显示每一个客户的现金余额,即此字段的值是由ORDERS. DB表中ItemsTotal和Amountpaid字段的值计算而来的。为使应用程序实现这种计算功能,完成以下步骤:

1、在Object Inspector中选择自定义字段对象Table1Balance,修改其 Calculated属性值为True。即定义Balance字段为计算字段。

2、在Object Inspector窗口中,选择Table1部件的Event页。

3、双击OnCalcField事件,为Table1OnCalcField编写事件处理过程如下:

procedure TForm1.Table1OnCalcFields(DataSet:TDataSet);

begin

Table1Balance.Value:=Table1ItemsTotal.Value-Table1AmountPaid.Value;

end;  

浏览ORDERS.DB 中的记录  

14.5 查询数据库中的记录

       数据库中储存着大量的数据信息,如何充分有效地查询其中的数据,对用户而言是至关重要的。如果想查询数据库,首先要确定要查询的字段要么是数据库表中的关键字段,要么是辅助索引。如果我们查询的是Paradox或dBASE数据库系统中的表,这是唯一的选择。

    一般而言,查询数据库中的记录的方法有两种:Gotokey方法和Findkey方法。两种方法十分相似,主要区别在于我们如何指定查找值。这两种方法的思想是在指定列(字段)中寻找指定的查找值,如果在数据库表中找到了这个值,表中的记录指针便指向该记录,这样我们便查询到了我们需要的记录,进而可以访问找到的记录中的各项数据。 

14.5.1 使用GotoKey方法查找数据记录 

使用Gotokey方法查询数据库中的记录的具体步骤如下:

1、确保要查找的字段是关键字或已经为它定义了辅助索引,并保证TTable部件的属性列表中有关键字段名或辅助索引名。

2、通过调用GotoKey方法,把要查找的TTable部件置成查找模式。

3、把查找值送进被查找的Field的查找缓冲区。

4、调用TTable部件的GotoKey方法,并测试它的返回值判断查找是否成功。

    如果查找成功,GotoKey返回一个True值,并且表中的记录指针指向找到的记录。 如果查找失败,GotoKey返回False,表中的记录指针不发生变化。

    在这里要注意的是如何给Field的查找缓冲区赋值, 我们知道字段对象是不可见的对象,它们没有自己的名字,在大多数情况下,要使用TTable部件的FieldByName 方法到字段列表中查找字段对象以便为它赋值。但字段缓冲区也是没有名字的,当TTable部件处于查找模式时,我们只要把查找值赋给字段对象的AsString属性就可以了。AsString的作用不只是它的表面意思。它是一个转换属性,任何赋给字段对象的AsString属性的字符串都将转换成该字段对象应于数据库表中的字段的数据类型。当然AsString不能将查找值转换成BLOB、Bytes、Memo和Graphic类型的数据,用户一般也不会查找这种数据类型的字段。

下面便是说明使用Gotokey方法查找数据记录的例子。

    例14.3 当用户在Edit1部件中输入客户号码并单击查找按钮,程序便开始在Table1中查找这个客户号。如果查找成功,查找信息“查找成功”便会显示在标签Label1上,被查询到的客户的电话号码显示在标签Label2上。表中的记录指针将转移到该客户记录处。并且在网格DBGrid1中以高亮度显示这一条记录。  

查询数据库中的记录

    下面的程序清单是查询按钮上的OnClick事件的处理程序,它是使用Gotokey方法查找数据库中的记录的。

procedure TForm1.Button1OnClick(Sender:TObject);

begin

with Table1 do

begin

Label1.Caption:=' ';

Label1.Caption:=' ';

IndexFieldName:='CustNo';

setkey;

FieldByName('CustNo').AsString:=Edit1.Text;

If GotoKey then

begin

Label1.Caption:='查找成功';

Label1.Caption:=FieldByName('Phone').AsString;

end;

else

Label1.Caption:='查找失败';

end;  

查询数据库中的记录 

14.5.2 使用FindKey方法查找数据库中的记录 

    虽然使用上面的Gotokey方法在数据库中查找记录效果不错,但是Delphi 还提供了一种更加容易的查找方法,这就是Findkey方法,两种方法虽然很相似,但是Findkey方法更简单明了一些。

例14.4 我们可以使Findkey方法代替上面例子中的处理程序,下面是程序代码:

procedure TForm1.Button1OnClick(Sender:TObject);

var

SeekValue:string;

begin

with Table1 do

begin

Label1.Caption:=' ';

Label1.Caption:=' ';

IndexFieldName:='CustNo';

SeekValue:=Edit1.Text;

If FindKey([SeekValue]) then

begin

Label1.Caption:='查找成功';

Label1.Caption:=FieldByName('Phone').AsString;

end;

else

Label1.Caption:='查找失败';

end;

        Findkey方法和Gotokey方法的根本区别在于查找值要作为参数传递给Findkey 函数。而GOtokey是不带参数的, 它假定用户已经把查找值赋给了代表着被查找到的字段的查找缓冲区。

        Findkey接受的参数是放在方括号中的,是用逗号分开的查找值数组。 数组中的每一个值都对应于特定列的查找值,即参数中允许有多个查找值,Findkey 允许用户同时查找数据库表中的多个列。上面的程序清单中的Findkey函数只接受了变量Seekvalue这一个查找值,这个查找值对应表中的字段CustNo,CustNo是表中的关键字段。如果要同时查找表中的多个字段,必须把要查找的多个字段名赋给TTable部件的IndexFieldName属性,并用逗号分开各字段,然后把每个字段的查找值赋给Findkey的参数数组中。 

14.5.3 利用GotoNearest和FindNearest执行不精确查找 

    在我们上面讨论的查找中,要么查找成功要么查找失败,因为我们查找的是特定查找值的一个精确匹配值。Delphi还提供了一种查找方法,即不精确查找,这样的查找绝对不会失败,它总是给用户查找出一个结果来,也许这结果并不是用户需要的,但这个查找出来的结果是最接近用户要求的。在Delphi中是利用GotoNearest和FineNearest两种方法来执行不准确查找的,它们总是从数据库中查找出与查找值最接近的匹配值。如果它们查找到与查找值精确匹配的值,那当然最好不过了,如果他们找不到精确匹配的值,它们就会把与用户指定的查找值最接近的记录提交给用户。

        GotoNearest的使用方法和Gotokey一样,FindNearest的使用方法和Findkey一样。跟Gotokey一样,使用GotoNearest时必须要把查找值赋给字段的查找缓冲区,两者的不同之处在于查找值的说明方式不一样,使用GotoNearest时, 说明的查找值可以是完整的也可以是不完整的,如果要对'Dunteman'进行不精确查找,在给字段的查找缓冲区赋查找值时,可以使用'Dunteman'、'Dun'或者`Du'作为查找值, 这样查找出来的结果会尽可能地接近这个值的。

     如果没有找到与用户指定的查找值精确匹配的记录,Delphi会调整记录指针并停留在与查找值最接近的第一个记录上。例如如果查找`Dunteman'时,没有找到精确匹配的值,记录指针可能会停留在`Dunwoody'上或者停留在更远一些的'Event'上;如果查找'Du' 没有找到精确匹配的值,记录指针可能停留在‘Duncan’上,甚至‘Dunteman'之前, 总之Delphi会自己地调整记录指针,使之指向最接近查找值的记录并将该记录作为查找的结果提交给用户。

         GotoNearest和FindNearest都返回一个Boolean值以表明查找是否成功。 它们一般都是成功的,它们总是要把记录指针移到某处。

下面的例子是用GotoNearest方法进行不精确查找。

    例14.5 创建好的窗体,在编辑框中输入一个不完整的客户所在的公司名称,并且按“不精确查找”按钮,然后观察一下查找的结果并注意记录指针指向那一条记录。反复试验几次便会理解GotoNearest是如何工作的。


想死你们了!

TOP

DELPHI基础教程

第十四章 简单数据库应用的创建及MASTAPP介绍(三)
    利用GotoNearest方法执行不精确查找

窗体中的“不精确查找”按钮的事件处理过程代码如下:

procedure TForm1.Button1Click(Sender: TObject);

begin

with table1 do

begin

IndexFieldNames:='Company';

setkey;

FieldByName('Company').AsString:=Edit1.text;

GotoNearest;

label3.caption:=FieldByName('Company').AsString;

end;

end;

    读者可以利用 FindNearest 方法执行上面的不精确查找, 具体使用方法可以参看Findkey方法的使用。

在上面的例子中要设置table1的IndexFieldNames属性为Company。

GotoNearest方法进行不精确查找

14.6 修改数据库中的记录 

    我们掌握了字段对象的概念和如何查找数据库中的记录之后,下面我便可以很方便地修改数据库中现存的记录了,一般来说,在程序中修改数据库中的记录包括下面这些步骤:

1、在数据库中找到要修改的记录,并将记录指针移至该记录。

2、调用Edit方法将与数据库表相连的TTable部件设置成编辑状态。

3、修改一个或多个字段。

4、调用post方法将修改后的记录写入数据库。

    以上这几个步骤只是概述性的,具体实现时还有很多细节需要留心,我们通过一个例子来演示上面的全过程,以便让读者进一步地了解和掌握修改记录的方法。

   例14.6 我们为四个按钮分别编写了事件处理过程,用来遍历数据库中的记录并对每个客户记录的Company字段进行修改, 在程序对记录进行更新操作时窗口中的控件都是无效的,在这个例子中我们还编写了一个简单的异常代码块用来确保在更新过程中出现异常时使控件恢复正常操作。 

修改数据库记录

14.6.1 Edit方法Post方法 

    为了能让用户通过程序修改数据库表中的记录,TTable部件必须要处在编辑状态下。在大多数情况下,数据库表都是以浏览(只读方式)方式打开的,也就是说它的每一个字段可以被读取介不能被编辑修改。调用Edit 方法能够将 TTable 部件置成编辑状态, 当TTable部件处于编辑状态后,我们才可以通过程序修改当前记录指针所指向的记录,但这样修改后的记录不会立即被写入到磁盘上的实际数据库表中。要想保存对记录的修改,必须要调用Post方法,Post方法才真正将我们对记录的修改写入实际的数据库表中。

一般来说,用来扫描整个数据库表并修改每个记录的某一个字段的程序如下所示:

with Table Do

begin

DisableControls;{在修改记录的过程中,使其它部件无效}

First; {将记录指针指向第一条记录}

while not EOF do

begin

<读取记录的一个字段值到一个变量中>

<做适当的修改>

Edit; {将TTable部件置成编辑状态}

<将修改后的字段值写回到其对应的字段>

post; {将修改后的记录写回数据库}

next; {修改下一条记录}

end;

enablecontrols; {恢复其它部件的功能}

end;

    程序都是对TTable部件进行操作,因此使用With语句来防止错误的扩散是很有意义的。在这里要注意Disablecontrols方法和EnableControls方法的使用。DisableControls方法是在程序修改TTable部件中的记录时,切断TTable部件与数据访问部件TDatasource 部件的联系。否则,在对TTable中的每一修改之后,TDataSource 部件都会更新窗体中所有数据浏览部件的显示内容,这样会急剧减慢处理过程而且浪费时间。EnableControls方法是与DisableControle方法执行相反的操作,它是用来恢复TTable部件与TDatasource部件的联系并促使所有的数据浏览部件更新显示。

    调用First方法是将记录指针移到数据库表中的第一条记录, 确保程序从表中的第一条记录开始进行修改。调用Next方法是将记录指针从当前的记录移到下一条记录,这样保证了从表中的第一条记录开始逐条记录进行修改,直到修改完最后一条记录。如果不调用Next方法,程序将会陷入无穷的死循环。 

14.6.2 实现异常保护的TRY...FINALLY语句 

    上面的程序存在着潜在的危险,在实际应用过程中,可能因为某些原因使得对数据库表的更新不能进行下去。如当程序试图执行Post方法将修改后的记录写回磁盘时,而又因为某种原因磁盘没有准备好,这时便出现了异常。当出现异常时,应用程序会暂停下来并且会弹出一对话框显示有关的错误信息,在用户单击错误信息对话框之后,程序将继续执行到某一个地方去,而这个地方常常不是用户所能预料到的。 在我们的程序中, 在执行Post方法之前,窗体中所有的部件与TTable部件都已失去联系。因此,这种异常将导致窗体中显示的数据和数据库无关。

        Object Pascal中的Try...Finally语句为我们解决上述异常问题提供了一个解决方法。在Delphi中仍然采用了这一语句用来处理异常问题。实际上,Try...Finally 语句是把两组语句组合在一起。语句的Try部分包含了可能产生异常的程序代码,Finally部分包含了即使发生了异常也必须执行的一条或多条语句。 在本例中, Finally 部分只包含了EnableControls方法调用这一条语句,我们将前面的代码改写并组合进Try...Finally 语句: 

with Table Do

begin

DisableControls;{在修改记录的过程中,使其它部件无效}

Try;

First; {将记录指针指向第一条记录}

while not EOF do

begin

<读取记录的一个字段值到一个变量中>

<做适当的修改>

Edit; {将TTable部件置成编辑状态}

<将修改后的字段值写回到其对应的字段>

post; {将修改后的记录写回数据库}

next; {修改下一条记录}

end;

enablecontrols;

Finally;{出现异常时,执行下面的程序}

enablecontrols; {恢复其它部件的功能}

end; {结束Try...Finally语句}

end;

    在保留字Try和Finally之间的代码跟前面的代码是一样的,它们用于在记录之间移动记录指针并处理对记录的修改,这一段代码可能会出现异常,当异常发生时,我们想保证执行EnableControls, 以便窗体中各控件恢复与 TTable 部件的联系, 因此我们必须将EnableControls语句放在Finally和结束语句End之间。

    在这里要特别注意,请读者们不要混淆了Try...Finally语句和Try...Except 语句。如果真正想在发生异常时采取相应的处理,就要使用Try...Except语句。Try... Finally语句只是用来处理当异常出现时,使应用程序执行Finally部分的语句, 使程序继续执行下去。Try...Except语句是实现异常处理,Try...Finally语句是实现异常保护。

    有了上述这些概念,我们便可以提供这个例子的一些程序代码,它涉及了所有这些内容。

程序清单:修改数据库中的记录 

unit Unit26;

interface 

uses

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

Dialogs, StdCtrls, Grids, DBGrids, ExtCtrls, DB, DBTables, Buttons; 

type

TForm1 = class(TForm)

DataSource1: TDataSource;

customerTable: TTable;

Panel1: TPanel;

DBGrid1: TDBGrid;

Panel2: TPanel;

UpperCaseFirstAddBtn: TButton;

UpperCaseSecondAddBtn: TButton;

MixedCaseFirstAddBtn: TButton;

MixedCaseSecondAddBtn: TButton;

BitBtn1: TBitBtn;

procedure ForceCase(TargetField:String;ToUpper:Boolean);

procedure UpperCaseFirstAddBtnClick(Sender: TObject);

procedure MixedCaseFirstAddBtnClick(Sender: TObject);

procedure UpperCaseSecondAddBtnClick(Sender: TObject);

procedure MixedCaseSecondAddBtnClick(Sender: TObject);

procedure FormCreate(Sender: TObject);

private

{ Private declarations }

public

{ Public declarations }

end; 

var

Form1: TForm1; 

implementation

const

upper=true;

Mixed=False; 

{$R *.DFM}

Function IsUpper(ch:char):Boolean;

begin

If (ch>='A')and(ch<='Z')then

IsUpper:=true

else

IsUpper:=False;

end;

procedure TForm1.ForceCase(TargetField:String;ToUpper:Boolean);

var

WorkBuffer:string;

i:Integer;

begin

with customerTable do

begin

DisableControls;

TRY

First; {将记录指针移到第一条记录处 }

While not EOF do

begin

WorkBuffer:=FieldByName(TargetField).AsString;

If ToUpper then

for i:=1 to Length(WorkBuffer)do

WorkBuffer:=UpCase(WorkBuffer)

else

begin

for i:=1 to Length(WorkBuffer) do

If IsUpper(WorkBuffer) then

WorkBuffer:=chr(ord(WorkBuffer)+32);

WorkBuffer[1]:=UpCase(WorkBuffer[1])

end;

Edit;

FieldByName(TargetField).AsString:=WorkBuffer;

post;

Next;

end;

Finally

enableControls;

end;

end;

end; 

procedure TForm1.UpperCaseFirstAddBtnClick(Sender: TObject);

begin

ForceCase('Addr1',Upper);

end; 

procedure TForm1.MixedCaseFirstAddBtnClick(Sender: TObject);

begin

ForceCase('Addr1',Mixed);

end;

 

procedure TForm1.UpperCaseSecondAddBtnClick(Sender: TObject);

begin

ForceCase('Addr2',Upper);

end;

 

procedure TForm1.MixedCaseSecondAddBtnClick(Sender: TObject);

begin

ForceCase('Addr2',Mixed);

end;

 

procedure TForm1.FormCreate(Sender: TObject);

begin

customerTable.open;

end; 

end. 

14.7 插入和删除记录 

    虽然我们使用DBD或者在应用程序窗体中用TDBNavigator可以插入、删除表中的记录,但是任何重要的数据库应用程序都是根据最终用户的命令完成此类操作的。同样,如果我们掌握了字段对象及其用法,修改数据库中的记录,插入和删除记录将变得非常容易。

    要想删除表中的某一条记录,首先将记录指针移到该记录处,然后调用delete方法,这样,当前指针所在的记录就会被删除,而且我们在进行删除操作时,不必将TTable部件设置成编辑状态。当前指针所在的记录被删除之后,被删除记录下面的所有记录都向前移动,记录指针自动移到紧挨着被删除的记录的下一条记录。在删除记录的过程中没有提醒用户是否真的想删除当前记录的信息确认框,因此在进行此项操作时要倍加小心,如果是开发应用程序,最好的办法是提供一个确认信息框确保用户不会意外删除记录。

    插入一条记录也很简单,Delphi为用户提供两种方法用来插入记录到现存数据库表中,一种方法是在当前记录指针所在的记录处插入记录;另一种方法是在数据库表的尾部插入记录。这两种方法是分别调用Insert方法和Append方法实现的。但是无论是调用Insert方法还是调用Append方法在具有索引的数据库表中插入记录,增加到索引表中的记录都将按照索引顺序写入到数据库表中,也就是说对于索引表,调用Insert和Append方法的效果是一样的。事实上,Append方法只适用于那些没有索引的表,这种没有索引的表并不十分有用因而通常不创建这种表。几乎任何情况下我们都是用Insert方法来插入记录。

    用户在插入记录时一般可以采用两种方式插入:逐步插入即首先建立一条空记录,然后再填充记录的各个字段,最后再将记录写回到磁盘,共分三个独立的操作步骤;而使用InsertRecord方法便可以一次将插入记录的操作完成。 

14.7.1 逐步插入方法 

    逐步插入方法分为三个明确的步骤:先调用TTable部件的Insert方法在TTable中创建一条新的空记录,然后填充该记录的各个字段,最后调用post方法把新记录写到磁盘上的实际数据库文件中,在填充并传送记录以前,考虑插入记录到表中的什么位置是毫无意义的,假设插入的表是有索引的,在调用post方法时,Delphi会自动地把插入的新记录按照索引顺序插入到表中的正确位置。如果插入的表中没有索引,那么新记录将插入到当前指针所在记录的后面。

因此,采用逐步插入方法插入记录的程序代码一般如下形式:

With Table do

begin

Insert; {插入一条空白记录}

<填充该记录的各个字段>

post; {将插入的记录写回到磁盘文件}

end;

对于没有索引的数据库表,可以用Append方法替代Insert方法把新记录插入到表的尾部。 

14.7.2 调用InsertRecord插入记录 

    对于简单的应用程序,Delphi允许用户用一条语句插入一个新记录,而且这个新记录可以带有任意多个新字段值。InsertRecord方法把新记录中字段的赋值语句和psot方法调用组合进一条语句中。

       InsertRecord方法把记录的各个字段值组合成一个字段值数组作为它的唯一参数。在字段值数组中,可以为插入的记录的每个字段提供一个字段值,或从最左一列开始依次为任意多个字段赋值。 也就是说用户可以从表的最左边一列起, 把多个列的值同时传递给InsertRecord,直到所有字段都被赋值。用户也可以省略后面的字段,InsertRecord会用空值填充这些没有赋值的字段。用户还可以对那些明确希望用空值填充的字段传递保留字NIL来标明该字段为空。

如我们希望在Customer.DB表中插入一条记录,可以用下面的代码来实现: 

InsertRecord(['2000',NIL,NIL,NIL]); 

在上面的程序代码中,我们只填充了四个字段:CustNo、Company、Add1 、 Add2 。InsertRecord会自动将其它字段赋以空值。

例14.7 在这个例子中,我们在CustNo.DB表中插入和删除记录,都是在程序中完成这类操作的,而不再是使用DBD或数据浏览部件完成。 

插入/删除记录 

程序清单:

unit tt; 

interface 

uses

SysUtils, Windows, Messages, Classes, Graphics, Controls,

StdCtrls, Forms, DBCtrls, DB, DBGrids, Buttons, DBTables, Grids,

ExtCtrls,Mask,Dialogs;

 

type

TForm1 = class(TForm)

DBGrid1: TDBGrid;

DBNavigator: TDBNavigator;

Panel1: TPanel;

DataSource1: TDataSource;

Panel2: TPanel;

customerTable: TTable;

BitBtn1: TBitBtn;

Label1: TLabel;

Label2: TLabel;

BitBtn2: TBitBtn;

BitBtn3: TBitBtn;

CustNoEdit: TEdit;

CompEdit: TEdit;

procedure FormCreate(Sender: TObject);

procedure BitBtn2Click(Sender: TObject);

procedure BitBtn3Click(Sender: TObject);

procedure FormActivate(Sender: TObject);

private

{ private declarations }

public

{ public declarations }

end;

 

var

Form1: TForm1;

 

implementation

 

{$R *.DFM}

 

procedure TForm1.FormCreate(Sender: TObject);

begin

customerTable.Open;

end;

 

procedure TForm1.BitBtn2Click(Sender: TObject);

begin

If (Length(CustNoEdit.text)=0)and

(Length(CompEdit.text)=0)

then

MessageDlg('没有输入新记录的字段值!',mtError,[mbCancel],0)

else

with customerTable do

begin

IndexFieldNames:='CustNo';

If FindKey([CustNoEdit.text]) then

MessageDlg('已经存在这条记录!',mtError,[mbCancel],0)

else

InsertRecord([StrToInt(CustNoEdit.text),CompEdit.text,nil]);

CustNoEdit.text:=' ';

CompEdit.text:=' ';

end;

 

end;

 

procedure TForm1.BitBtn3Click(Sender: TObject);

begin

If (Length(CustNoEdit.text)=0)and

(Length(CompEdit.text)=0)

then

MessageDlg('没有输入删除的记录的字段值!',mtError,[mbCancel],0)

else

with customerTable do

begin

IndexFieldNames:='CustNo';

If FindKey([CustNoEdit.text]) then

begin

If MessageDlg('你确定要删除这条记录吗?',mtConfirmation,

[mbYes,mbno],0)=mrYes then Delete;

end

else

MessageDlg('没有你要删除的记录!',mtError,[mbCancel],0);

CustNoEdit.text:=' ';

CompEdit.text:=' ';

end;

end;

 

procedure TForm1.FormActivate(Sender: TObject);

begin

CustNoEdit.setfocus;

end; 

end. 

14.8 输入数据的有效性验证 

    当用户向一个数据库表中插入新记录或修改原有记录时,我们必须确保用户输入的数据是有效的,为此Delphi通过三种不同的途径用来验证用户输入的数据是否有效。

   这三种途径是:基于数据库表的有效性验证、基于字段的有效性验证、基于记录的有效性验证。

基于数据库表的有效性验证:

   在用户创建数据库表时就建立有效性验证机制,如在使用DBD创建一个表时, 我们可以为创建的数据库表说明一些验证手段,包括字段的最大值,最小值,图形字段的显示格式等等。在设定这些有效性验证机制时,不需要编写任何程序代码。基于数据库表的有效性验证是当数据写到数据库之前,由数据库本身来执行。Delphi也执行一些有效性验证,如在数据写到数据库之前Delphi会验证每一个字段是否被填入相应的值,有关这种途径来验证数据的有效性的详细情况请参考DBD的使用。

基于字段的有效性验证:

一般有两种方法来进行这种方式的有效性验证。

①为记录中需要设置有效性验证的字段编写Onvalidate事件处理过程。这样每当该字段的值被修改时,该字段的OnValidate事件处理过程就会被调用,进而对被修改的字段值进行验证。

②对于记录中要求非空的字段(如口令或关键字等),我们必须首先设置这些字段的Required属性为True,然后为这些字段编写OnValidate事件处理过程,这样在修改现存记录或插入新记录时,在写入数据库之前,如果要求非空的字段中没有填入适当的字段值,那么会出现错误信息提示用户必须输入字段值。

基于记录的有效性验证:

这种验证方式一般在TTable部件的BeforePost事件处理过程中进行处理,即在记录写回到数据库之前对记录的每个字段值进行有效性验证。

例14.8 在程序中对字段值的有效性进行验证。

1. 创建一个用TEdit部件浏览ORDERS.DB表的应用,如图14.25所示。

2. 修改TDataSource部件的AutoEdit属性为True。

3. 双击TTable部件打开字段编辑器Fields Editor,并单击SaleDate字段。

4. 在Object Inspector中双击SaleDate字段对象的OnValidate事件, 为该字段对象编写事件处理过程如下: 

TForm1.Table1SaleDateValidate(Sender:TField);

begin

If SaleDate.Value>Now then

raise Exception.Create('不能输入一个未来的日期');

end;

    当这个应用程序运行时,用户修改或插入ORDERS.DB中的记录时, 该应用程序会对销售日期(SaleDate)字段的值进行验证,该字段值不能晚于系统的当前日期,程序中调用Now方法获得系统的当前日期。如果字段值大于系统的当前日期会出现一错误信息提示框,告知用户不能输入一个未来的日期。

使用TDBComBox部件和TDBLookupComBox部件来限制用户输入字段值的范围。

    创建查看orders.db表的应用,创建好的窗体如图14.25所示。窗体中显示Terms 字段的是TDBComBox部件,显示EmpNo字段的是TDBLookupComBox部件。 

图14.25 用数据浏览部件限制用户的输入 

TDBComBox和TDBLookupComBox部件的属性值如表14.8所示: 

表14.8 窗体中各部件的属性设置

━━━━━━━━━━━━━━━━━━━━━━━━━━━

部 件 属 性 属 性 值

───────────────────────────

DataField Terms

DBComBox1 DataSource DataSource1

Items Prepaid

Net 30

COD

───────────────────────────

DataField EmpNo

DataSource DataSource1

DBLookupComBox LookupSource DataSource2

KeyField EmpNo

LookupField EmpNo

───────────────────────────

DataSource1 DataSet Table1

AutoEdit True

───────────────────────────

DataSource2 DataSet Table1

AutoEdit True

───────────────────────────

Table1 DatabaseName DemosDB

TableName orders.db

───────────────────────────

Table2 DatabaseName DemosDB

TableName orders.db

━━━━━━━━━━━━━━━━━━━━━━━━━━━

    该应用运行时,当用户修改和插入记录到ORDERS.DB表中时,Terms字段的值可以从组合框中的Prepaid、Net30、COD三个值中任选,EmpNo字段的值是从另一个表Employee中获得的雇员号码,用户可以从中选择。

 


想死你们了!

TOP

DELPHI基础教程

第十五章 数据访问部件的应用及编程(一)

    在这一章里我们主要介绍Delphi的数据访问部件的层次结构、 多部件之间的关系、部件的属性、方法、事件以及各部件的应用。这些部件包括:

● TSession部件

● 数据集部件(TTable和TQuery)

● TDatasource部件

● 字段对象TField

● 字段编辑器的使用

● TReport部件和TBatchMove部件

我们对这些部件的属性、方法和事件进行一般性的描述,读者在实际使用Delphi开发应用程序时,还可以通过联机帮助获得有关部件更详细的信息。 

15.1 Delphi数据访问部件的层次结构 

        Delphi提供了强大的开发数据库应用程序的能力,它给用户提供了大量的数据访问部件。以方便程序设计人员开发数据库应用程序。这些部件中,有些部件继承了另一些部件的属性、方法和事件,也就是说多部件之间存在着继承和被继承的关系,各部件的这种关联便构成了一个层次结构 

图15.1 Delphi数据访问部件的层次结构 

TSession是全局性的部件,在应用程序运行时,它自动地建立,在设计阶段和运行过程中它是一个不可见的部件。

TDatabase部件是为开发客户/服务器数据库应用程序时,设置登录的数据库的有关参数的,它在数据访问部件页上。

TDataset部件是不可见的,TTable和TQuery部件是由它派生而来的,这两个部件一般被称为数据集部件,它们在数据访问部件页上。

TDatasource部件是连接数据集部件和数据浏览部件的桥梁,它在数据访问部件页上。

TFields部件对应于数据库表中的实际字段,它既可以在应用程序的运行过程中动态地生成也可以在程序设计阶段用字段编辑器创建。它是不可见的部件,在程序中我们可以通过TField部件来访问数据库记录的各个字段值。 

15.2 Tsession部件及其应用 

TSession部件一般用得较少,但它对于一些特殊的应用是很有用的,在每一个数据库应用程序运行时Delphi自动地创建一个TSession部件。程序设计人既不能看见该部件也不能显示地创建一个TSession 部件,但是我们可以在应用程序中全局性地使用TSession部件的属性、方法。 

15.2.1 TSession部件的重要属性及作用 

TSession部件的许多重要属性是用于控制数据库应用程序与数据库的连接的,在一个应用程序中,可以全局性地设置TSession的有关属性值,对与之相连接的磁盘上的数据库进行控制。TSession部件主要有下列属性:

Database属性:是TSession中可以进行连接的所有数据库的数据库名字列表,这些数据库的名字常常是实际数据库的别名,包括数据库的路径、用户名、用户登录口令等参数。

DatabaseCount属性:是TSession中可以进行连接的所有数据库的数量,它是一个整数。

KeepCounnections属性:是一个布尔型属性,用它说明应用程序是否保持与一个非活动数据库的连接。因为对于一个数据库,当该数据库中没有相应的数据集部件(TTable或TQuery)被打开时,该数据库将自动地变成非活动的数据库。缺省情况下,KeePcounnections的值是True,就是说应用程序总是保持着与数据库的连接, 即使数据库变成了非活动的数据库时,也是如此。如果将KeepConnections属性设置成False,那么当数据库由活动状态变成非活动状态时,应用程序与该数据库的连接也随之中断。

NetFileDir属性:说明BDE网络控制文件的路径名。

PrivateDir属性:说明存取临时文件的路径名。 

15.2.2 TSession部件的方法: 

TSession部件中的大部分方法是用于向用户提供与应用程序相连接的数据库的信息,如数据库的名字及别名,数据库中的表名以及数据库引擎BDE的有关参数等,在设计数据库应用程序时,想要获取有关数据库的信息,调用TSession部件的下列方法, 将会大大简化程序的设计。

GetAliasNames方法:调用该方法,我们可以获得数据库引擎BDE中定义的数据库别名。

GetAliasParams方法:该方法主要用于获取我们在BDE中定义数据库别名时所说明的参数值,如BDE所在的目录路径以及实际名称等。

GetDatabaseNames 方法:调用该方法可以帮助我们获得当前应用程序可以进行连接的所有数据库的名字,数据库的名字是用户使用BDE工具定义的实际数据库的别名。

GetDriverNames方法:数据库引擎BDE可以与多种数据库管理系统相连接,如客户/服务器数据库管理系统Oracle、Sybase以及本地数据库管理系统dBASE,Paradox等,BDE与每一种数据库管理系统进行连接时,都有相应的驱动程序,而且这些驱动程序都可以选择地安装。通过调用GetDriverNames方法。我们可以获得当前BDE安装的数据库驱动程序的名字。

GetDriverParams方法:BDE的数据库驱动程序中包含着多个参数,如支持的民族语言、DBMS的版本号、文件块大小等,对于服务器上的DBMS,还有数据库服务器的名字等等。

GetTableNames方法:因为每一个数据库都是由多个数据库表组成的,我们通过说明数据库名,然后调用GetTableNames方法,便可以获得该数据库中全部的数据库表的名字。

上述这些方法在调用时都需要一个字符串列表作为参数, 而且都返回一个字符串列表的值。

TSession部件还有一个叫DropConnections的方法用于控制应用程序与数据库的连接,当调用DropConnections方法时,应用程序与所有的数据库的连接将会切断。 

15.2.3 TSession部件应用举例 

例15.1:我们创建一个应用程序,通过调用TSession有关的方法获取当前应用程序可以进行连接的数据库的名字以及获取其中任意一个数据库中的全部数据库表的名字。 

通过TSession部件获取数据库的有关信息 

窗体中主要使用了两个列表框,其中列表框DatabaselistBox用于显示数据库的名字,列表框TablelistBox用于显示数据库中的表名。程序运行完后数据库的名字显示在DatabaselistBox列表框中,当用户单击DatabaselistBox列表框中的数据库名时,该数据库全部的数据库表的名字将会显示在TablelistBox列表框中。有关的程序代码如下: 

程序清单15.1

unit unit31; 

interface 

uses

SysUtils, Windows, Messages, Classes, Graphics, Controls,

Forms, Dialogs, StdCtrls, DB, DBTables, Buttons, ComCtrls, Tabnotbk; 

type 

TQueryForm = class(TForm)

BitBtn1: TBitBtn;

DataSource1: TDataSource;

Table1: TTable;

GroupBox1: TGroupBox;

CheckBox1: TCheckBox;

CheckBox2: TCheckBox;

PageControl1: TPageControl;

TabSheet1: TTabSheet;

Label1: TLabel;

Label2: TLabel;

Label3: TLabel;

ListBox1: TListBox;

ListBox2: TListBox;

ListBox3: TListBox;

TabSheet2: TTabSheet;

Memo1: TMemo;

procedure FormCreate(Sender: TObject);

procedure ListBox1Click(Sender: TObject);

procedure ListBox2Click(Sender: TObject);

end;

 

var

QueryForm: TQueryForm;

 

implementation

 

{$R *.DFM}

 

uses RSLTFORM;

 

procedure TQueryForm.FormCreate(Sender: TObject);

begin

Screen.Cursor := crHourglass;

 

{ Populate the alias list }

 

with ListBox1 do

begin

Items.Clear;

Session.GetAliasNames(Items);

end;

 

{ Make sure there are aliases defined }

 

Screen.Cursor := crDefault;

if ListBox1.Items.Count < 1 then

MessageDlg( 'There are no database aliases currently defined. You ' +

'need at least one alias to use this demonstration.',

mtError, [mbOK], 0 );

end;

 

procedure TQueryForm.ListBox1Click(Sender: TObject);

var

strValue: string; { Holds the alias selected by the user }

bIsLocal: Boolean; { Indicates whether or not an alias is local }

slParams: TStringList; { Holds the parameters of the selected alias }

iCounter: Integer; { An integer counter variable for loops}

begin

 

{ Determine the alias name selected by the user }

 

with ListBox1 do

strValue := Items.Strings[ItemIndex];

 

{ Get the names of the tables in the alias and put them in the

appropriate list box, making sure the user's choices are reflected

in the list. } 

ListBox2.Items.Clear;

Session.GetTableNames(strValue, { alias to enumerate }

'', { pattern to match } 

15.3.4 数据集中的数据维护 

数据集中的数据维护主要包括数据记录的修改,插入和删除。Delphi为数据集部件提供了相应的方法用于其中的数据维护。这些方法如表15.所示。 

表15.3 Delphi用于数据维护的方法

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

方 法 名 功 能

──────────────────────────────

Edit 将数据集置为编辑状态

──────────────────────────────

Append 投寄所有被修改的记录,将记录指针移到表中的最后

一条记录,且将数据集置为插入状态

──────────────────────────────

Insert 投寄所有被修改的记录将数据集置为插入状态

──────────────────────────────

Post 将插入的新记录和修改的记录写回磁盘上的数据库表,

即投寄,当投寄成功时数据集回到浏览状态,若投寄

不成功数据集仍然保持原有状态

──────────────────────────────

Cancel 取消当前的操作且将数据集置为浏览状态

──────────────────────────────

Delete 删除当前记录指针所在的记录且将数据集置为浏览状态

──────────────────────────────

AppendRecord 在表的最后插入一条新记录,记录的各个字段值作为

AppendRecord的参数传递给新记录

──────────────────────────────

InsertRecord 在当前指针所在记录的后面插入一条新记录, 记录的

各个字段值作为InsertRecord的参数传递给新记录。

──────────────────────────────

SetRecords 修改当前记录,字段名和相应的字段值作为SetRecords

的参数

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 

Edt方法:如果应用程序想对数据集中的数据记录进行修改,我们必须要将数据集设置成编辑状态。调用数据集部件的Edit方法便可以将数据集置成编辑状态,当数据集已经处在编辑状态时,调用Edit方法不会产生作用。当数据集处于编辑状态时,移动记录指针或调用post方法都可以将当前记录的修改写回到磁盘数据库表中。在程序中, Edit方法和post方法常常配合在一起使用,用于修改表中的记录。如: 

Table1.Edit;

Tabel1.FieldByName('CustNo').Asstring := '1234';

Table1.st; 

在上述这一段程序代码中,第一行程序是将Table1置成编辑状态,第二行程序是对当前记录指针所在的记录的CustNo字段的值修改成'1234',第二行程序是调用post方法将对当前记录的修改写回数据库表。

Append方法和Insert 方法:这两个方法都是将数据集部件置成插入状态,以在表中插入新记录,Insert方法是在当前指针位置的记录后面插入一打新记录,Append方法是在表的尾部插入一打新记录,不过这要注意,无论用户是调用Insert方法还是Append方法插入新记录,增加记录到一个具有索引的表中时,都是按照索引顺序写入其位置,也就是说对于索引表格Insert方法和Append方法的作用是一样的,Append仅适用于没有索引的表。Insert方法和Append方法实际上是将数据集置成插入状态,并且插入一条空白记录,要真正插入一条新记录,我们必须在调用Insert或Append方法之后,还要给新记录的各个字段赋值,最后调用post方法,将插入的记录写回数据库表。调用这两种方法插入新记录的一般步骤如下: 

With tabe1 DO

Begin

Insert; {调用Insert方法,插入一条空记录}

<为记录的各字段赋值>

Post;

End; 

Post方法:数据集中的记录被修改或插入新记录时调用post方法将数据集的修改写回到数据库表。根据数据集所处的状态不同,post方法所产生的作用和效果是不一样的:

● 当数据集处于编辑状态时,调用post方法,将当前记录的修改写回数据库表

● 当数据集处于插入状态时,调用post方法,将插入的新记录写回数据库表

● 当数据集处于SetKey状态时,调用post方法,将数据集置成浏览状态(Browse状态)

 

post方法的调用既可以显式地调用,也可以隐含地调用,当数据集处于编辑状态或插入状态时,当移动记录指针时,Delphi会隐含地调用post方法,将将当前记录的修改写回数据库表,在程序调用Insert方法或Append方法时,也会隐含地调用Post方法,将先前的数据集的修改写回数据库表。

Delete方法:Delete方法用于删除表中的记录,调用Delete方法时,将会删除表中当前的记录,并且自动地将记录指针移到被删记录的下一条记录,同时将数据集置成Browse状态。

Cancel方法:Cancel方法用于取消当前的操作,当程序还没有调用Post方法,将对记录的修改写回数据库表时,调用Cancel方法,可以将记录恢复到没有修改之前的状态。并且在调用Cancel方法时,它总是将数据集置成Browse状态。

AppendRecord方法和InsertRecord方法:这两个方法分别与Append方法和Insert方法相似。它们都是用于在表中插入一条新记录,但AppendRecord方法和InsertRecord方法比Append和Insert方法更简单更方便一些,它们直接在表中插入一条新记录,新记录的各个字段值作为AppendRecord或InsertRecord方法的参数传递给新记录并且不需显式地调用post方法,将插入的新记录写回数据库表。在给插入的新记录赋字段值时,将由多个字段值组成的数组作为AppendRecord或InsertRecord的参数,在字段值数组中可以为每一个字段提供一个值,或从左边一列开始依次为任意多个字段赋值。也就是说,用户可以从数据库表的最左一列起,把许多列的值同时传递给InsertRecord,直到所有的字段被赋值,用户也可以省略字段序列后面的的一些字段值,InsertRecord会用空值来填充这些字段:用户也可以对那些明确希望用空填充的字段传递保留字NIl。

例如:如果表Country有Name,Captial,Continent,Area和Population字段, 并且数据集部件Table1与它相连,下面的代码便可以在Country表中当前记录的后面插入一条新记录。

 

Table1.InsertRecord (["中国","北京","五洲"]);

 

在上述代码中没有为Area和population字段赋值,InsertRecord会用空值来填这两个字段。

SetRecords方法:调用该方法可以修改表中当前记录的多个字段的值,调用该方法之前必须将数据集部件置成编辑状态,调用该方法之后,还要调用post方法,才能真正将当前记录的修改写回数据库表。调用SetRecord方法时,被修改的字段值必须要与表中实际存在的字段名对应,并且数据类型要相匹配。例如,下面的代码是修改上面刚刚插入的那条记录。

 

Table1.Edit;

Tabel1.SetRecord(, , ,9600000,1200000000);

Tabel1.post;

 

这一段代码是修改上面刚刚插入的那条记录的Area 和Population 字段的值,而对Name,Continent和Captial字段没有修改。

在数据集部件中,还有一个重要方法Abort方法,该方法是用于取消其他方法的调用的,如在插入记录、修改记录和删除记录之前,往往需要用户确认是否真的要执行这种操作,此时调用Abort方法便可取消各种方法的调用,下面的代码是在用户删除一条记之前,让用户确认是否真的要执行删除操作。 

Tabel1.BeforeDelete(DataSet:TDataSet);

If MessageDlg('真的要删除记录吗?',

mtConfirmation,mbyesNoCanel,0 <> mryes then

Abort; {取消删除操作} 

关于书签(BookMark)操作;

书签操作主要用于在表中快速地定位记录指针,在应用程序中常常要保存记录指针所在的位置,在进行其他处理之后,希望能快速地返回到先前指针所在的位置,此时,使用书签将显得特别有用。有关书签操作,Delphi提供了三个方法,它们是:

● GetBookMark

● GotoBookMark

● FreeBokMark

 

这三个方法一般都是在一起使用,GetBookMark方法返回一个TBookMark类型的变量,该变量包含着指向当前记录的指针,GotoMark方法用于快速地将记录指针定位到具有书签的记录处。FreeBookmark方法是与GetBookMark方法相反的操作,它释放书签标志。下面的程序代码阐述了书签操作的一般方法:

 

BookMark : TBookMark;

<Do something>

BookMark := Table1.GetBookMark; {对当前记录作书签标志}

Table1.DisalbeControls; {切断Table1与数据察觉部件的联系}

Table.First

While Not EOF Do {对表中全部记录进行其他处理}

begin

<Do something>

Tabel1.Next;

end;

Tabel1.GotoBookMark(BookMark)

Table1.enableControls; {重新定位记录指针回到原来的位置}

Tabel1.FreeBookMark(BookMark); {删除书签BookMark标志} 

15.3.5 数据集部件与数据浏览部件的连接 

      数据集部件TTabel和TQuery具有三个方法,DisableControls 方法、EnableControls方法、Refresh方法用于控制数据集部件和与其相连的数据浏览部件之间的连接,以及控制数据浏览部件的显示。在用户修改和更新以及遍历数据库表中的记录时,调用DisableControls方法具有重要意义,调用DisbaleControls方法以切断TTable或TQuery部件与数据浏览部件的连接,使数据浏览部件暂时失效,否则,在对TTable或TQuery部件的每次修改之后,窗体中所有与它们相连的数据浏览部件都要更新其显示内容,这亲显然会减慢处理速度。当遍历表中的记录时记录指针每移动一下,窗体中的数据浏览部件也随之更新一下其中的显示内容,在屏幕上产生闪烁。

EnableControls方法的作用与DisbaleControls方法的作用是相反的,调用EnableControls方法,使TTable或TQuery部件恢复与数据浏览部件的连接,使暂时失效的数据浏览部件恢复到正常显示表中记录信息的状态。

Refresh方法用于刷新数据浏览部件中的显示。在调用Refresh方法时,必须要确保TTable或TQuery部件是打开的。当数据集中的记录被修改之后,调用Refresh方法,数据浏览部件中显示的信息也随之改变。

 


想死你们了!

TOP

DELPHI基础教程

第十五章 数据访问部件的应用及编程(二)
15.3.6 数据集部件的事件 

    数据集部件TTable或TQuery具有很多的事件。为这些事件编写相应的程序代码可以进行有效性验证、计算可计算字段的值、确认对数据库表的多种操作等等。这些事件及其描述如表15.4所示。 

表15.4 数据集部件常用的事件

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

事 件 描 述

───────────────────────────────────

BeforeOpen,Afteropen 在数据集部件被打开之前/之后被触发

───────────────────────────────────

BeforeClose,Afterclose 在数据集部件被关闭之前/之后被触发

───────────────────────────────────

BeforeInsert,AfterInsert 在数据集部件进入插入状态之前/之后被触发

───────────────────────────────────

BeforeEdit,AfterEdit 在数据集部件被编辑之前/之后被触发

───────────────────────────────────

BeforePost,AfterPost 在数据集部件投寄被修改的记录之前/之后被触发

───────────────────────────────────

BeforeCancel,AfterCancel 在数据集部件取消前一步操作之前/之后被触发

───────────────────────────────────

BeforeDelete,AfterDelete 在数据集部件删除当前记录之前/之后被触发

───────────────────────────────────

OnNewRecord 当建立一条新记录时被触发

───────────────────────────────────

OnCalcFields 当为表中的计算字段计算字段值时被触发

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 

15.4 TTable部件及应用 

在前一节里我们介绍了数据集部件TTable 和TQuery 的共同的一些属性和方法。TTable部件是Delphi数据库编程中要经常使用的最重要的部件之一,它是数据库应用程序访问数据库时必须使用的数据集部件之一,在这一节里,我们重点介绍TTable部件特有的属性和方法,TTable部件所有的属性、方法和事件都可以在联机帮助中查到。 

15.4.1 TTabel部件主要的属性 

DatabaseName属性和TableName属性:

DatabaseName属性是说明数据库应用程序所操作的数据库的名字,它可以是由BDE定义的数据库的别名、显式说明的数据库文件所在的磁盘路径或者由TDatabase部件定义的一个数据库名。DatabaseName属性常常是一个由BDE定义的数据库的别名。使用由BDE定义的数据库的别名代替数据库实际所在的路径和名字,好处是当实际的数据库存放的位置发生变化时,只需利用BDE简单地设置一下该数据库的别名,而数据库应用程序无需修改。有关BDE的使用请参看BDE的设置应用。TabelName属性用以说明当前TTable部件所连接的实际的数据库表。这两个属性一般都在设计阶段指定,当然在程序运行过程中也可以设置,但是要修改这两个属性时, 必须要在TTabel的Active属性为False时进行,当TTable的Active属性为True时,这两个属性是不能被修改和设置的。

TableType属性:

该属性说明与TTable部件相连接的数据库表的类型。当TableType属性设置成Default时,该属性所说明的数据库表的类型由数据库文件的扩展名决定。

● 若数据库文件的扩展名为.DB或没有扩展名,表的类型是Paradox表

● 若数据库文件的扩展名为.DBF时,表的类型是dBASE表

● 若数据库文件的扩展名为.TXT时,表的类型是ASCII表 

如果TableType属性不设定为Default,那么与TTable 部件相连的数据库表的类型由TableType中的设置的值决定,不用考虑数据库文件的扩展名。

KeyExclusive属性:

该属性的一个作用是说明在数据库表中查找记录时,将记录移到与查找值相匹配的记录处还是将记录指针移到与查找值相匹配的记录后面一条记录处。 该属性是布尔型变量,当它的值为False时(缺省情况下为False), 将记录指针移到相匹配的记录处,为True时,将记录指针移到相匹配记录的后面一条记录处。该属性另一个作用是在表中指定检索范围时,用来说明是否包括满足过滤条件的边界记录。当KeyExclusive的值为False时,检索范围包括边界记录,否则不包括边界记录,有关详细的操作请参看“限定表中记录的检索范围”。

IndexFields属性和IndexFieldsCount属性:

IndexFields的属性值是数据库表中字段名列表,它包含与TTable部件相连的数据库表中的全部索引字希。IndexFieldsCount属性说明表中索引字段的个数。这两个属性值都是只读的,只有在程序运行过程中可用。

IndexName属性和IndexFieldNames属性:

IndexName属性中存放着在建立数据库表时为数据库表定义的所有辅助索引名,它是一个辅助索引名列表,是只读属性。IndexFieldNames属性指定用于数据库表索引排序的字段名,多个字段名之间用分号隔开。例如对Customer.DB表中的客户记录按邮政编码ZipCode和客户号码CustNo排序时可以设定IndexFieldNames的值为:

ZipCode ; CustNo

在IndexFieldNames属性中指定的字段必须存在于相应的数据库表中,否则会导致错误。IndexName和IndexFieldName是互斥的,每次只能指定其中一个属性的值,不能同时为两个属性都指定属性值。

Exclusive属性:

该属性是一个布尔型属性,它标明是否以共享方式打开数据库表,如果Exclusive的值为True,当打开一个数据库表时,其他用户就不能访问该表了,若Exclusive的值为False,将以共享方式打开一个数据库表。 显然不能将其他用户正在访问的表以互斥方式打开(设定Exclusive的值为True)。对于SQL数据库服务器上的数据库表,当以互斥方式被一个用户打开时,其他用户可以读取该表中的数据,但不能修改表中的数据,当然有些数据库服务器不支持这种方式,这要具体参看有关的数据库服务器的文档。

ReadOnly属性和CanModify属性:

这两个属性都是布尔型属性,ReadOnly属性决定用户是否能够对表中的数据进行读写。ReadOnly为True 时,用户只能读取表中的数据,ReadOnly为False时,用户可以读写表中的数据(假设数据库已授权用户能够读写其中的数据库表)。CanModify属性是一个只读属性,用户不能够修改其属性值,它反映了用户对数据库表拥有的实际特权,当ReadOnly为True时CanModify将自动地被置为False,当ReadOnly为False时,如果数据库允许用户对表进行读写时,CanModify为True,否则CanModify为False。当CanModify为False时,数据库表是只读的,但不能将其置成编辑状态或插入状态;当CanModify属性为True时,虽然数据库表对应的数据集部件可以置成编辑和插入状态,但是这并不意味着用户能够插入和修改表中的数据,因为这还要受到其他因素的限制,如用户对SQL数据库服务器的访问权限等的限制。

TTable部件还有其他一些属性请参看联机帮助

 

15.4.2 TTable部件的方法及应用

 

15.4.2.1 设定数据库表的使用范围

 

在我们实际应用中的数据库表中常常存放着大量的数据信息,其中包含着很多的记录,而我们的应用程序可能只需对其中一部分记录进行操作,因此,为应用程序指定一个使用范围就显得特别重要了,为方便有效地指定数据库表的使用范围Delphi为TTable部件提供了下列方法供用户使用:

● SetRangeStart和EditRangeStart方法

● SetRangeEnd和EditRangeEnd方法

● SetRange([Start Values],[End Values])方法

● ApplyRange方法

● CancelRange方法

 

1. SetRangeStart方法

用于指定检索范围的起始记录,调用SetRangeStart方法之后,可以为起始记录的一个或多个字段指定相应的字段值。SetRangeEnd方法用于指定检索范围的结束记录,调用SetRangeEnd方法之后,可以为结束记录的一个或多个字段指定相应的字段值。

 

2. SetRange方法

SetRange方法包含了SetRangeStart和SetRangeEnd方法的功能,它可以同时指定检索范围的起始和结束记录,起始记录和结束记录的字段值以数组形式送给SetRange,其基本形式是:

SetRange([起始值],[结束值])

 

3. ApplyRange方法

根据SetRangeStart,SetRangeEnd或SetRange方法说明的检索范围的起始和结束记录,具体设定一个检索范围,调用ApplyRange方法之后, 应用程序只能对检索范围内的记录进行有关的操作。

 

4. CancelRange方法

CancelRange方法的作用与ApplyRange方法的作用是相反的,这是取消为表设定的检索范围,调用CancelRange方法之后应用程序可以对表中全部记录进行有关的操作。

在这里要注意的是:如果我们使用的是paradox表或dBASE表,在调用SetRangeStart,SetRangeEnd以及SetRange方法时,只能为表中的索引字段或定义的索引指定相应的字段值,以设定检索范围。如果使用SQL数据库服务器中的数据库表,可以为IndexFieldNames属性中指定的字段指定相应的字段值。

例如:假设Table1与Customer.DB表相连,Customer.DB中一个索引字段是CustNo,同时应用窗体中有两个编辑框StartVal和EndVal用于输入起始、结束记录的字段CustNo的值,下面的程序代码便可以为我们设定一个检索范围:

 

Tabel1.SetRangeStart; {指定检索范围的起始记录}

Tabel1CustNo.AsString:= StartVal.Text {为起始记录的CustNo字段指定字段值}

Tabel1.SetRangeEnd; {指定检索范围的结束记录}

if EndVal.Text <> ' ' then

Tabel1CustNo.AsString := EndVal.Text; {为结束记录的CustNo 字段指定字段值}

Tabel1.ApplyRange; {根据检索范围的起始、结束记录设定检索范围}

 

注意上面的程序代码,在为结束记录的CustNo字段指定字段值时, 首先检查EndVal的值是否为空,如果EndVal的值为空,那么设定的检索范围没有包含一条记录, 因为没有任何记录的字段值小于NIL;如果StartVal的值为空,那么检索范围将从表中的第一条记录开始,因为表中任何记录的字段值都大于空(NIL)。

上述代码可以用SetRange方法改写成:

 

If EndVal.Text <>' ' then

Tabel1.SetRane([StartVal.Text].[EndVal.Text]);

Table1.ApplyRange;

 

EditRangeStart和EditRangeEnd方法的使用完全类似于SetRangeStart和SetRangeEnd方法,只是调这两个方法是设定一个可编辑的范围。

又如:假设一个表中的一个索引包含两个字段LastName和FirstName,我们为索引中的一个字段或多个字段指定相应的字段值,设定数据库表的使用范围。

 

Table1.SetRangeStart;

Table1.FieldByName('LastName').Asstring := 'Smith';

Table1.SetRangeEnd;

Tabel1.ApplyRange;

 

上述代码设定的范围包括LastName字段的值大于或等于Smith的所有记录。而下面的代码设定的范围则包括LastName字段的值大于或等于Smith且FirstName字段的值大于或等于'J'的记录。

 

Table1.SetRangeStart;

Table1.FieldByName('LastName').Asstring := 'Smith';

Table1.FieldByName('FirstName').Asstring := 'J';

Table1.SetRangeEnd;

Tabel1.ApplyRange;

 

15.4.2.2 查找数据库表中的记录

 

如果想查找数据库表中的记录,必须想指定查找记录的一些字段的字段值,然后在表中进行检索,检索出与查找值相匹配的记录来。如果我们是在Paradox或dBASE数据库中的表中查找记录,那么查找值所对应的字段必须是表中的关键字段或辅助索引字段。如果查找SQL数据库服务器中的表,那么查找值必须是表的IndexFieldNames属性中指定的字段。

Delphi提供了两种方式在数据库表中查找记录:Goto方式和Find方式。这两种方式十分相似,它们的主要区别在于为查找指定查找值的方法不一样。

使用Goto方式进行数据查找使用的方法有SetKey方法、GotoKey方法和GotoNearest方法。其实际步骤如下:

 

①确保要查找的字段是关键字段或辅助索引字段。

②调用SetKey方法把与表对应的TTable部件置成查找状态。

③把查找值赋给相应的字段。

④调用GotoKey方法,并测试它的返回值检验查找是否成功。

 

假设Table1对应的表中第一个字段是关键字段,Edit1是应用窗体中的一个编辑框,用户可以通过Edit1输入查找值。下面的代码将通过Goto方式进行查找。

 

Table1.SetKey; {将Table1置成查找状态}

Table1.Field[0].AsString := Edit1.Text; {指定查找值}

Table1.GotoKey; {进行查找}

 

上面最后一行代码是根据用户指定的查找值,在表中执行查找。查找的结果有两种,也许成功也许失败,这是由调用GotoKey方法之后返回的布尔值来决定,如果返回True,那么查找成功,并且记录指针会指向与查找值匹配的记录,如果返回Fale,那么查找失败,记录指针的位置不发生变化。下面的代码可以测试调用GotoKey方法之后的返回值,告知用户查找是否成功。

 

Table1.SetKey;

Table1.Field[0].AsString:= 'Smith';

If not Table1.GotoKey then

ShowMessage('记录没找到')

 

在这一段代码中,如果在表中没有找到第一个字段值为Smith的记录,该应用程序会弹出一个对话框告知用户“记录没有找到”。

如果在表中存在多个关键字段或辅助索引中包含多个字段时,你在进行查找时,只想为第一个字段指定查找值,那么必须要设置TTable部件的KeyFieldCount的属性值为1。如果想为多个字段指定查找值,只能为相邻的字段指定查找值,例如辅助索引中共有三个字段,那么我们只能为第一个字段、第一和第二个字段、第一和第二以及第三个字段指定查找值,而不能为第一和第三个字段指定查找值。

GotoNearest方法的使用与GotoKey方法完全一样,只是它用于不精确查找,它不要求查找结果与查找值精确匹配,当表中有与查找值精确匹配的记录时,它将记录指针移到该记录处,当表中没有与查找值精确匹配的记录时,它会查找出与查找值最接近的记录,并将记录指针移到该记录处。

下面是应用GotoNearest方法的一段代码:

 

Table1.SetKey;

Table1.Fields[0].AsString:= 'Sm';

Table1.GotoNearest;

 

执行上述代码后,若表中存在第一个字段值等于Sm的记录时,记录指针将移到该记录处,若表中不存在第一个字段值等于Sm的记录,而存在第一个字段值等于Smith的记录,那么记录指针会移到该记录处。

如果我们不是以数据库表中的关键字段作为查找字段,我们也可以为TTable部件的IndexFieldName属性中的字段或IndexName属性中的字段指定查找值进行数据查找。例如,假设Customer表中有一个名叫CityIndex的辅助索引,我们为CityIndex中的字段指定查找值进行查找时,首先设置TTable部件的IndexName属性为CityIndex,然后再进行查找,下面是具体的程序代码:

 

Table1.IndexName := 'CityIndex';

Table1.Open;

Table1.SetKey;

Table1.FieldByName{'City').AsString := Edit1.Text;

Table1.GotoKey;

 

使用Find方式:使用Find方式在数据库中进行数据查找的方法有:FindNearest方法和FindKey方法。

FindKey方法和FindNearest方法为数据查找提供了一个简单的方法,它们将SetKey、指定查找值、执行查找三个步骤融合在一步里完成,它们在指定查找值时,是把各字段的查找值组成一个数组传给FindKey或FindNearest。下面是使FindKey方法的一个例子。

假设Tabel1对应的表中的第一个字段是关键字段。

 

Table.FindKey([Edit1.Text]);

 

如果用GotoKey方法完成这一功能则需要编写下面代码:

 

Table1.SetKey;

Table1.Fields[0].AsStrine := Edit.Text;

Table1.GotoKey;

 

FindKey方法和FindNearest方法的区别与GotoKey和GotoNearest方法的区别是一样的。

 

15.4.2.3 创建主要──明细数据库应用

 

TTable部件中MasterSource属性和MasterFields属性是用于定义两个数据库表的一对多的关系。MasterSource属性指定主表对应的TDataSource部件,MasterFields属性指定主表和明细表之间建立联系的字段,主表和明细表之间建立一对多关系时,可能不只是基于一个字段,可能有多个字段。如果有多个字段,那么在说明MasterFields属性时,多个字段之间要用分号隔开。如Table1.MasterFields := 'OrderNo;CustNo'。在设计阶段可以使用字段连接设计器(Field Link Designer)为两上表创建一对多的关系,在Object Inspector 中双击TTable部件的MasterFields便可以打开Field Link Designer,进行一对多关系的创建。 如创建Customer.DB表和Order.DB表之间的一对多关系时,使用Field Link Designer 如图15.5所示。

 

 

 

图15.5 使用Field Link Designer创建一对多关系

 

Field Link Designer提供了一种可视化的方法来创建主要──明细表之间的一对多关系。图中Available Indexes组合框中存放着明细表中的关键字段和索引字段,可以选择索引字段进行连接。在主表中选择一个用于连接的关键字段,然后将其与明细表中相应的关键字段连接,单击Add按钮,主要──明细表的连接字段将显示在Joined Fields列表框中,如:

CustNo->CustNo 

15.5 TDataSource部件及其应用

 

TDataSource部件是开发数据库应用程序中用到的非常重要的部件,它是连接数据集部件TTable或TQuery和数据浏览部件的桥梁。TDataSource部件本身十分简单,它所拥有的属性、事件和方法都比较少,在使用该部件时无需作太多的工作,它主要是为数据浏览部件服务的,如果在应用程序中没有使用数据浏览部件,我们也没有必要为应用程序设置TDataSource部件。

 

15.5.1 TDataSource部件的属性

 

TDataSource部件除了其他部件都拥有的Name属性和Tag属性之外,主要有下面几个属性:

DataSet属性:该属性说明TDataSource部件从中获取数据的数据集的名字,它可以是TTable部件的名字,也可以是TQuery部件的名字,甚至还可以指定其他窗体内的数据集作为该属性的值,如在下面的程序中我们指定窗体Form2中的table1作为窗体Form1中的DataSource1的DataSet属性值:

 

TForm1.Formcreate(Sender : Tobject);

Begin

DataSource1.DataSet := Form2.Table1;

end;

 

Enable属性:Enable属性可以暂时性地切断TDataSource部件和与之相连的数据集部件的连接。这是一个布尔型变量。当它的值为False时,TDataSource部件和数据集部件的连接被切断,且所有与TDataSource部件相连的数据浏览部件中将变为一片空白,不显示任何数据信息。当Enabled的值变为True时,TDataSource部件和数据集部件的连接恢复,且与TDataSource部件相连的数据浏览部件恢复显示数据。不过要实现上述这些功能,一般不使用TDataSource部件的Enabled属性,而是调用数据集部件的DisableControls方法和EnableControls 方法,因为调用这两个方法可以方便地控制与数据集部件相连的所有TDataSource部件以及与TDataSource部件相连的数据浏览部件。

AutoEdit属性:这是一个布尔型变量,它用于说明是否将与TDataSource部件相连的数据集置于编辑状态。当AutoEdit的值为True时,应用程序运行时,与TDataSource相连的数据集部件自动地被设置成编辑状态,当用户在与TDataSource部件相连的数据浏览部件中输入新的值时,数据集部件中的记录也随之改变。如果AutoEdit的值为False,用户想通过数据浏览部件或程序修改数据集中的记录,必须要调用数据集部件的Edit方法,将其置为编辑状态之后才能够进行。

 

15.5.2 TDataSource部件的事件

 

TDataSource部件具有三个事件:

● OnDataChange事件

● OnStateChange

● OnUpdataData

 

OnDataChange事件:当与TDataSource相连的数据集中的记录指针的位置发生改变时,该事件就被触发,也就是说当程序调用数据集部件的Next、Previous、Insert、Append等方法导致记录指针的位置发生改变时,便会触发该事件。该事件一般用于保持应用中多个部件之间的同步。

OnUpdataData事件:当数据集部件中当前记录将要被修改时,触发该事件。例如在程序调用post方法之后但在修改后的数据记录真正被写回磁盘中的数据库文件之前触发该事件,在应用中使用非数据浏览部件时要它与数据集保持同步时常使用该事件进行相关的处理。

OnStateChange事件:当与TDataSource部件相连的数据集部件的状态发生改变时, 便触发该事件。因为数据集部件的State属性标明了数据集部件当前所处的状态,当数据集的状态发生变化时,使用该事件进行有关的处理是很有用的,在一个具体的应用中, 数据集部件的状态常常是频繁地变化的,为了跟踪数据集部件的状态变化, 可以用下面例子中的程序代码将数据集部件当前的状态显示在一个标签上:

 

TForm1.DataSource1OnStateChange(Sender : Tobject);

var

S : String;

begin

Case Table1,State of

dsInactive : S := 'Inactive';

dsBrowse : S := 'Browse';

dsEdit : S := 'Edit';

dsInsert : S := 'SetKey';

dsSetKey : S := 'SetKey';

end;

Label1.Caption := S;

end;

 

类似地我们也可以通过检测数据集部件的状态来控制有关的按钮和菜单项是否有效。 例如:在一个应用窗体中有一个InsertBtn按钮,用于控制向数据集部件table1对应的数据库表中插入记录;还有一个CancelBtn按钮用于控制是否取消用户对当前记录的修改或插入新记录。下面的程序代码根据Table1的状态来控制这两个按钮的功能(是否有效,在窗体是否变灰暗)。

 

Form1.DataSource1OnStateChange(Sender : Tobject);

begin

InsertBtn.Enabled := (Table1.State = dsBrowse);

CancelBtn.Enabled := Table1.State in [dsInsert,dsEdit,dsSetKey]

end;

 

上面的代码中,当Table1处于浏览状态(Browse状态时), 用户是不能够向数据库表中插入新记录的,此时InsertBtn按钮将变灰暗即无效。当Table1不处于Browse状态时,InsertBtn按钮有效,用户是可以向表中插入新记录。同理,只有当Table1处于特入状态(Insert状态)或编辑状态(Edit状态)或查找状态(SetKey状态)时,CancelBtn按钮才有效,也即用户可以取消当前插入的记录、修改当前的记录以及查找到的结果等。

 

15.6 字段部件和字段编辑器的使用

 

字段部件有时又称字段对象它对应着数据库表中的列即字段,字段对象是不可见的部件,在Delphi中有两种方式创建字段部件:

①在应用程序运行过程中,随着数据集部件被激活,对应于数据库表中每一列的字段部件便动态地被创建。

②在设计过程中,程序设计人员利用字段编辑器(Fields Editor)可以创建永久性的字段部件,即使字段对象对应的数据库表的结构发生了变化时,这些字段部件也不会发生变化。

既然字段部件是对应于数据库表中的各个字段的,而数据库表中的字段有多种数据类型,所以字段部件相应也有多种类型,字段部件的类型与数据库表中的字段的数据类型的对应关系如表15.5所示。

 

表15.5 字段部件的类型

━━━━━━━━━━━━━━━━━━━━━━━━━━━━

字段部件的类型 对应的数据类型

────────────────────────────

TStringField 字符串类型的字段

TSmallIntField 短整数类型的字段 -32768-32767

TIntegerField 整数类型的字段

TWordField 正整数类型的字段0-65535

TBooleanField 布尔型字段

TFloatField 浮点数类型的字段

TCurrenCyField 货币型字段

TDataField 日期型

TTimeField 时间型

TBCDField 小数位数固定的浮点数

TDataTimeField 日期时间型字段

━━━━━━━━━━━━━━━━━━━━━━━━━━━ 

我们在本书中只介绍一些常见类型的字段部件的使用,其他类型字段部件的使用可以参看联机帮助文件。

 


想死你们了!

TOP

DELPHI基础教程

第十五章 数据访问部件的应用及编程(三)

15.6.1 字段部件 

字段部件在应用程序中始终是不可见的部件。在程序运行过程中是如此,在程序设计阶段也是如此,但是它在应用中起着非常重要的作用,可以说它是所有数据浏览部件从数据库表中显示、编辑数据的基础。这是因为字段部件直接对应着数据库表中的字段,浏览和修改表中的数据必须要通过字段部件,同时字段部件所拥有的属性可以用来说明数据库表中对应的字段的数据类型、当前的字段值、显示格式、编辑格式等,字段部件的事件如OnValidate可以用来设定输入字段值时进行有效性检验。

数据库表的每一列在应用程序中都有其对应的一个字段部件,在缺省情况下,当TTable或TQuery的Active属性被置为False或调用close方法时,与表中各列对应的字段部件也随即消失,要想为应用程序创建永久性的字段部件,我们必须要在程序设计阶段使用字段编辑器(Fields Editor)来创建。使用字段编辑器创建永久性字段的好处是:我们在程序代码中利用永久性字段部件可以更加有效、方便、可靠地访问数据库表中记录的各字段值, 在任何时候我们都可以以同样的字段顺序、固定的字段显示表中的记录,即使数据库表的结构已发生了变化。当然如果在数据库表中与字段部件对应的字段已经不存在时,应用程序就不能正常地执行下去了,Delphi会弹出一个错误信息框,告诉用户表中的字段已经不存在了。 

15.6.1.1 字段部件的属性及应用 

字段部件具有很多的属性,通过设置字段部件有关的属性,可以控制字段对象在数据浏览部件中的显示方式、字段值能否被修改等。特别是对于用字段编辑器创建的永久性的字段部件,我们在程序设计阶段便可以在Object Inspector中方便地选取字段部件, 进行有关属性的设置。

字段部件的主要属性如表15.6所示,该表中列出的属性只是字段部件的部分属性,它主要用来控制字段对象的显示方式。 

表15.6 字段部件的主要属性

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

属性名 功 能

───────────────────────────────

Alignment 说明字段值在数据浏览部件中显示时的对齐方式:

左对齐、右对齐、居中三种方式。

───────────────────────────────

Calculated 说明字段是否是计算字段,属性值为True时,该

字段是计算字段、字段值可以根据表中其它字段

的值计算得出。

───────────────────────────────

Currency 等于true时,以货币格式显示数值,等于False时,

不以货币格式显示数值型数据。

───────────────────────────────

DisplayFormat 用于说明字段值在数据浏览部件中的显示格式

───────────────────────────────

DisplayLabel 字段在网格(TDBGrid部件)中显示时,为字段指定

显示标题。

───────────────────────────────

DisplayNidth 字段在网格(TDBGrid部件)中显示时,为字段指定

显示宽度,单位是字符数。

───────────────────────────────

EditFormat 说明字段在数据浏览部件中的编辑输入格式

───────────────────────────────

EditMask 在进行字段值的编辑输入时,限定输入字段值的

过滤条件(即字段值的范围)。

───────────────────────────────

FieldName 该字段部件对应实际数据库表中的字段的名字

───────────────────────────────

Index 该字段部件在数据集所有字段部件中的顺序号

───────────────────────────────

MaxValue 说明可以为该字段输入最大的数值

───────────────────────────────

MinValue 说明可以为该字段输入最小的数值

───────────────────────────────

Name 字段部件的名字

───────────────────────────────

ReadOnly 等于true时,只能读取该字段的字段值,不能修改;

等于False时,可以对该字段的字段值进行读写。

───────────────────────────────

Size 说明字段的大小,单位是字符数

───────────────────────────────

Visible 为True时,该字段可以在TBDBGrid部件中显示;

为False时,该字段不能在TDBGrid部件中显示

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

 

表15.6中的属性并不是所有类型的字段部件都拥有的,如一个TStringField类型的字段部件是没有Currency、MaxValue、MinValue和DisplayFormat属性的,一个TFloatField类型的字段部件是没有Size属性的。

对于布尔型属性,在设计过程中的Object Inspector中双击该属性,该属性的值将会在True和False之间来回切换,其他属性需要用户输入属性值或从下拉式列表框中选取属性值。所有的属性都可以通过程序代码进行设置。大多数属性可以独立地设置,只有DisplayFormat,EditFormat和EditMask是相互联系的。在设置它们的属性值时一定要确保相互协调。

利用EditMask属性为字段设定编辑模式:

为字段部件设置一定的EditMask属性值,当编辑输入该字段的字段值时,用户只能根据EditMask设定的编辑模式进行编辑或输入字段值。在为EditMask属性设置属性值时可以用手动方式也可以用输入模式编辑器来完成,当为某字段部件设置EditMask属性时,双鼠标双击EditMask属性便可以打开输入模式编辑器(Input Mask Editor) 。例如在为Customer.DB表的Phone字段设定编辑模式时,首先在Object Inspector中选取与Phone字段对应的Table1Phone字段对象,然后双击EditMask属性,打开输入模式编辑器。 

字段输入模式编辑器 

在字段输入模式编辑中可以选择一种输入模式,而且在TestInput编辑框中输入字段值进行检验。

因为TStringField类型的字段部件没有DisplayFormat属性,但是可以把EditMask属性当DisplayFormat属性使用。

设定字段的显示和编辑格式:

Delphi本身为某些类型的字段对象提供了设定其显示和编辑格式的例程,并且为字段部件的DisplayFormat和EditFormat属性指定了缺省值,例如对于与浮点型数值字段对应的TFloatField类型的字段部件,而且该字段部件的Currency属性设置为True 时,字段值1234.56的显示格式为$1234.56,编辑格式是1234.56。表15.7是Delphi提供了设置字段显示和编辑格式的例程。 

表15.7 字段格式例程

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

例 程 名 运用的字段对象

─────────────────────────────

FormatFloat TFloatField,TCurrencyField

FormatDateTime TDateField,TTimeField,TDateTimeField

FormatInteger TIntegerField,TSmallIntField,TWordField

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

 

上述这些用于设定日期时间类型、数值型以及货币型字段的显示和编辑格式的例程,都是按国际上通行格式来设定相应类型字段的格式的,用户可以自己设置字段部件的DisplayFormat和EditFormat属性,来设定适合自己使用的格式,还可以为有关字段对象的OnGetText和OnSetText事件编写代码来设定字段的显示和编辑格式。

 

15.6.1.2 字段部件的事件及应用

 

字段部件常需处理的事件如表15.8所示

 

表15.8 字段部件的事件

━━━━━━━━━━━━━━━━━━━━━━━━━━━━

事件名 用 途

────────────────────────────

OnChange 当字段部件的字段值发生改变时,触发该事件

OnGetText 当字段部件获得字段值时,触发该事件

OnSetText 当字段部件被设置字段值时,触发该事件

OnValidata 当字值被修改或插入新的字段值时,对字段值

进行有效性检验时,触发该事件

━━━━━━━━━━━━━━━━━━━━━━━━━━━━

 

用户想自己设定字段的显示和编辑格式时,可以编写OnGetText事件和OnSetText事件的处理过程,以达到设定字段的显示和编辑格式。

 

15.6.1.3 字段部件的类型转换函数及使用

 

字段部件具有一些内部函数用于转换字段值的类型,对于不同的字段类型,这些转换函数的作用是不一样的,表15.9概括了不同类型的字段及转换函数的作用。

 

表15.9 字段部件的转换函数

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

字段类型 AsString AsInteger AsFloat AsDatetime AsBoolean

────────────────────────────────────

TStringField 转换成 转换成整数 若能转换 日期 转换成布型

Stringg型 (若能转换) 则转换成 (若能转换)

────────────────────────────────────

TIntegerField

TSmallField 字符型 整数型 浮点型 不允许 不允许转换

TWordField

────────────────────────────────────

TFloatField

TCurrencyField 字符串型 舍入成整数 浮点型 不允许 不允许

TBCDField

────────────────────────────────────

TDateField

TDateTimeField 字符串 不允许 浮点数 日期型 不允许

TTimeField

────────────────────────────────────

TBooleanField 转换成Time 不允许 不允许 不允许 布尔型

或False

────────────────────────────────────

TBytesField

TVarBytesField 字符串 不允许 不允许 不允许 不允许

TBlobField

────────────────────────────────────

TMemoField 二进制 不允许 不允许 不允许 不允许

TGraphilField 字段

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

 

上述这些转换函数可以在任何与字段部件有关的表达式中使用,只要是表15.9中允许进行转换的数据类型,这些转换函数其实是当做字段部件的属性来使用的,它们可以出现在赋值语句的两边。例如下面的程序代码是将字段部件TableMyField的字段值转变成字符串类型的数据,并将它赋给编辑框Edit1的Text属性:

 

Edit1.Text := TableMyField.AsString;

 

而下面的代码是进行相反的操作,它将编辑框部件Edit1的Text属性值以字符串的形式赋给字段TableMyField,TableMyField通过AsString接受字符串并将其转变成自身的数据类型。

 

TableMyField.AsString :=Edit1.Text;

 

15.6.1.4 字段部件的访问

 

字段部件对应着数据库表中实际的字段,用户要读写数据库表中的字段值其实是通过访问相应的字段部件进行的。在前面的章节中我们介绍过在Delphi的数据库应用程序中有两类字段部件:一类是利用字段编辑器创建的永久性字段部件;另一类是随着数据集部件被激活(被打开)而动态生成的字段部件。对于永久性字段部件的访问可以直接调用使用字段部件的名字进行。假设我们在设计阶段利用字段编辑器创建了对应于Customer.DB表中Company字段的字段部件Table1Company,下面的代码访问Company字段的字段值,并将该字段值显示在编辑框部件Edit1中。

 

Edit1.Text := Table1Company.Value;

 

因为company字段是字符串类型的数据,它与Edit1中的数据类型相匹配的,因此可以直接使用字段部件的Value属性读取字段值。如果两个变量的类型不匹配,则要使用表15.9中的转换函数进行字段值的读取。例如:要读取Customer.DB表中的CustNo字段的值并将它显示在编辑模框Edit1中,假设我们已用字段编辑器(Fields Editor)创建了CustNo相应的字段部件,Table1CustNo,则程序代码如下:

 

Edit1.Text := Table1CustNo.AsString;

 

访问动态生成的字段部件相对要困难一些,因为动态生成的字段部件是没有自己的名字的,我们必须利用特殊的手段获得数据库表中各字段对应的字段部件,然后对字段进行访问。一般采用的方法有两种:

● 使用数据集部件的Fields属性

● 使用数据集部件的FieldByName方法

 

1. 使用数据集部件的Fields属性访问数据库表中各字段

数据集部件的Fields属性是与数据集部件相连的数据库表中各个字段对应的动态字段部件的名字列表,因此我们可以通过Fields属性的下标(即索引号)来访问各字段部件,从而达到访问数据库表中的各个字段,索引号从0开始,也就是说数据库表中第一个字段对应着Fields列表的第一行即0索引,第二个字段对应的Fields的索引号为1,以此类推。下面的例子是访问Customer.DB表中的第一个字段并在编辑框Edit1中显示其字段值。假设Table1与数据库表Customer.DB相连。

 

Edit1.Text := Table1.Fields[0].AsString;

 

下面的代码是将编辑框Edit1中的字符值赋给Customer.DB表中当前记录的第一个字段,以实现修改Customer.DB表中的字段值。

 

Table1.Fields[0].AsString := Edit1.Text;

 

2.使用数据集部件的FieldByName方法访问字段部件

在数据集部件所拥有的方法中,有一个FieldByName方法,它是专门用于访问数据集部件中动态生成的字段部件的,调用FieldByName方法时,必须要把数据库表中的字段名作为参数传给FieldByName,调用该方法后便可以得到该字段所对应的字段部件,这样通过字段部件我们便可以读写表中相应的字段值了,用这种方法访问字段部件时,必须要知道数据库表中各个字段的名字,否则是没有办法调用该方法的。还是基于上面的假设。下面是访问Customer.DB表中的CustNo字段的程序代码:

 

Edit1.Text := Table1.FieldByName('CustNo').AsString;

Table1.FieldByName('CustNo').AsString := Edit1.Text;

 

在使用这两种方法访问动态生成的字段部件时,可以使用表15.9中的转换函数,在变量和字段值之间进行数据类型的转换。

 

15.6.2 字段编辑器的使用

 

字段编辑器(Fields Editor)主要是用于创建永久性的字段部件。在前面的内容中我们知道,当TTable或TQuery部件与数据库表相连接时,且TTable或TQuery部件被激活时(Active属性被设置成True或调用Open方法),Delphi便动态地为表中各字段创建相应的字段部件,字段部件中包含着相应字段的很多信息如字段值、字段值的显示、编辑格式等,有时我们在应用程序中为了更加方便、可靠地访问数据库表中各个字段,需要创建永久性的字段部件,这时我们必须要借助于字段编辑器来实现我们的设想。字段编辑器的主要功能如下:

● 创建永久性的字段部件

● 修改永久性字段的显示属性,如显示格式、显示宽度等

● 删除永久性的字段部件

● 增加新的永久性的字段部件

● 定义计算字段(不对应数据库表中实际的字段,字段值根据表中其他字段的值计算得出)

 

15.6.2.1 打开字段编辑器

 

为TTable和TQuery部件打开字段编辑有两种方法:

● 用鼠标左键双击TTable或TQuery部件

● 选择TTable部件或TQuery部件,然后单击鼠标右键,然后从弹出式菜单中选择 Fields Editor

 

字段编辑器Fields Editor被打开以后,窗体的名字和数据集部件的名字会显示在窗口的标题上。 

字段编辑器Fields itor中的Fields列表框是用于显示已经创建的永久性字段部件的名字的。字段编辑器Fields Editor第一次被打开时, 该列表框是空的,因为在此之前的字段部件都是动态生成的,只要Fields列表框中有字段部件,那么与数据集部件相连的数据浏览部件中只显示Fields中列出的字段的字段值,在Fields列表框中,可以通过拖放字段部件的名字来改变相应的字段值在数据浏览部件中的显示顺序,如在TDBGrid部件中根据各字段在Fields列表框中的顺序显示各字段的值。

在字段编辑器Fields Editor窗体上面的导航按钮是用来移动TTable或TQuery部件中的记录指针的,使用导航按钮可以将记录指针向前、向后移动,也可以移到第一条记录处或最后一条记录处。 

字段编辑器中的弹出式菜单 

15.6.2.2 增加字段部件 

字段编辑器Fields Editor中的Add Fields菜单项用于向数据集部件中增加字段部件的,单击Add Fields菜单项时便会打开增加字段部件对话框,如图15.9所示。Available Fields列表框中显示出数据集部件TTable或TQuery中当前可以用于创建永久字段部件的全部的字段,也就是说Available Fields列表框中显示字段是数据库表中实际存在的字段,而且还没有为这些字段创建相应的永久性的字段部件,在缺省状态下所有的字段都被选择用于创建相应的永久性的字段部件,用鼠标单击其中的字段名可以有选择地创建其相应的永久性的字段部件,选择好有关的字段名之后,单击OK按钮便可以创建永久性的字段部件。 

字段编辑器的增加字段部件对话框  

15.6.2.3 删除字段部件 

用字段编辑器Fields Editor为数据集部件创建好的字段部件都会显示在字段编辑器的Fields列表框中,如果用户认为其中的一些字段部件不合适或不再需要时,可以单击这些不需要的字段部件,然后单击鼠标右键弹出一佣弹出式菜单,从弹出式菜单中选择Delete菜单项,便可删除相应的字段部件,如果在弹出式菜单中单击Select All菜单项,然后选择Delete菜单项,这样会删除已创建好的所有的字段部件。某一个字段部件被删除以后,通过单击Add Fields菜单项可以重新创建,只是先前为该字段部件设定的一些属性将不复存在。

 15.6.2.4 定义新的字段部件 

字段编辑器Fields Editor中的弹出式菜单中New Fields菜单项是用来为数据集部件TTable或TQuery创建用于显示目的的新的字段部件,我们可以用它来为数据库表中实际存在的字段创建新的字段部件(如改变字段的数据类型,使它的字段值被显示时不再需有关的类型转换),但是我们使用New Fields菜单项创建新的字段部件主要是创建计算字段。计算字段并不与数据库表中实际存在的字段对应,它的字段值是根据表中其它的字段值计算而来的,具体的计算表达式由用户为TTable部件或TQuery部件的OnCalCFields事件编写程序代码时决定。

定义(创建)计算字段的过程如下:

1.单击字段编辑器中的New Fields菜单项,定义字段对话框如图15.11所示。

2.在FieldName编辑框中输入新字段部件的名字,或者从下拉式列表框中选择一个已 存在的字段部件的名字。

3.在FieldType列表框中为新字段部件选择一个字段类型。

4.单击Calculated检查框,确认定义的新字段部件是计算字段。

5.单击ok按钮,创建上述定义的计算字段部件,此时该字段部件的名字会自动地加入 到字段编辑中的Fields列表框中。 

创建新的计算字段

新的计算字段创建好了之后,它是没有任何字段值的,我们必须要编写相应的程序代码,根据数据库表中实际存在的字段的字段值为计算字段的宝定义字段值,我们为计算字段所在数据集部件的OnCalcFields事件编写代码来为计算字段赋值,其步骤如下:

1.选择数据集部件TTable或TQuery

2.单击数据集部件的事件页

3.双击OnCalcFields事件为TTable或TQuery部件编写事件处理过程

 15.7 TReport部件及其应用 

在一般的数据库应用程序中都包含着为最终用户提供输出报表的功能,使用Delphi开发数据库应用程序时,可以使用一个叫TReport的部件来执行报表功能的,报表的具体格式和内容是由Delphi提供的一个专用报表生成工具ReprotSmith创建的,它报表的具体格式和内容生成一个报表文件,然后为TReport部件设置相应的属性参数,由TReport部件执行报表功能。

我们可以在设计阶段双击TReport部件,调用ReportSimith工具或者在Delphi程序组内双击ReportSmith图标来调用ReportSmith工具来创建一个报表文件,具体的操作步骤和设计方法请参看ReportSimth工具的使用说明。

我们在使用TReport部件执行报表功能时,要设置TReport部件的一些的一些属性,这些属性是:

ReportName属性:说明报表文件的名字,就是用ReportSmith创建的报表文件。

ReportDir属性:说明报表文件所在的途径名。

PreView属性:这是一个布尔型属性。若它的值为True,那么在执行报表功能时,只是在屏幕上显示报表;若它的值为False,则报表内容将在缺省的打印机打印出来。

AutoUnload属性:布尔型属性,它的值为True时,在执行完一个报表功能后,自动地从内存中卸出ReportSmith工具;它的值为False时,在运行完一个报表功能后,不从内存中卸出ReportSmith工具。一般情况下,如果应用程序只有一个报表或者只有较少的报表要输出时,应设置AutoUnload属性为True,如果应用程序一次要输出多个报表,那么要应设置AutoUnload属性为False。

InitialValues属性:这是一个字符串类型的属性,它是说明报表文件中使用的变量,每一条说明一个变量。如:

 

ReportVAR := Value;

 

要详细了解创建和使用报表变量的过程请参看创建报表一节。

TReport部件要真正执行报表功能以输出一个报表需要调用Run方法。如下所示:

 

Report1.Run;

 

TReport部件所具有的重要方法如表15.10所示。

 

表15.10 TReport部件的方法

━━━━━━━━━━━━━━━━━━━━━━━━━━━━

方法 功 能

────────────────────────────

Run 执行报表功能,输出报表

RunMacro 发送一个宏命令给Reportimith工具

Connect 预先连接报表文件和数据库,在输出报表时不

需要登录到数据库

SetVariable 改变说明的报表变量

ReCalcReport 当报表变量改变以后,重新输出报表

━━━━━━━━━━━━━━━━━━━━━━━━━━━━

 

还有一些其他的数据访问部件如TBatchMove部件, 它主要用在两个数据库表之间移动或拷贝帆数据记录,具体的使用请参看本地SQL服务器的使用。

 

15.8 应用举例:多个窗体显示同一个数据库表

 

在应用当中,我们常常需要以不同的视图显示同一个数据库表中的内容,例如要在两窗体中同时显示一个数据库表中一个记录的不同字段时,我们必须要想办法使两个窗体中的数据浏览部件同步地显示数据库表中的同一条记录的不同字段的值。要想做到以不同的视图显示同一个数据库表中的记录,下面两条规则是很重要的:

● 多个TDataSource部件能够同时访问同一个数据集部件

● 在多个窗体中显示同一个表时,必须为每个窗体设置一个TDataSource部件,只须 为其中的一个窗体设置一个TTable部件

 

例如,如果想在窗体Form1和Form2中同时显示一个数据库表的记录,最简单可行的办法是:为Form1和Form2各设置一个TDataSource部件叫DataSource1、DataSource2,并在Form1中设置一个TTable部件Table1,连接Form1中的Datasource1和Table1,在程序运行过程中设置Form2中的DataSource2的DataSet属性为Form1中的Table1,代码如下:

 

Format2.DataSource1.Dataset := Form1.Table1;

 

这样,当Table1被打开时,两个窗体中便可以同步地显示数据库表中的同一条记录了。

一个名叫TWOForms.DPR的例子在C:\Delphi\DEMos\DB\TwoForms中(如果Delphi安装在其它的磁盘驱动器中,从相应的磁盘驱动器中可以找到该例子),它演示了在两个窗体中显示同一个数据库表的记录。应用程序在第一个窗体中打开Contry.DB表,并在窗体中显示Name、Captial和Continent字段,在第二个窗体中显示Area和Population字段,在第一个窗体中有一个按钮用于打开第二个窗体,两个窗体中都有TDBNavigator部件,用于记录的导航。


想死你们了!

TOP

DELPHI基础教程

第十六章 数据浏览部件的应用及编程(一)

数据浏览部件主要用于显示和编辑数据库表中的数据,因而它们又常常被称为数据控制部件或数据明了部件,它们在部件选择板中的DataControls页上,图16.1显示的是DataControls页上的全部数据浏览部件,其中的TDBGrid部件用于全屏幕显示和编辑数据库表中的记录,TDBNavigator用于在数据记录之间导航、插入记录、删除记录、投寄被修改的记录。 

图16.1 DataControls部件页上的部件 

在表16.1中,我们对DataControls部件上的各个数据浏览部件的一些特性进行描述: 

表16.1 各数据浏览部件概述

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

部 件 名 称 特 性 描 述

────────────────────────────────

TDBGrid 用网格的形式显示数据库表中的记录信息,网格中的

各列可以在设计阶段使用字段编辑器创建也可以在运

行过程中用程序设定

────────────────────────────────

TDBNavigator 它提供了一组按钮用于数据库表中的导航,编辑修改、

插入、删除记录以及刷新数据的显示,TDBNavigator中

包含的控制按钮在设计阶段可以进行选择

────────────────────────────────

TDBText 用于显示数据库表中当前记录的字段值

────────────────────────────────

TDBEdit 用于显示和编辑数据库表中当前记录指定的字段值

────────────────────────────────

TDBMemo 用于显示数据库表中的备注型字段,备注型字段中可

以包含多行字符甚至可以是BLOB(大二进制对象)数据

────────────────────────────────

TDBImage 用于显示数据库表中的图像字段和BLOB数据

────────────────────────────────

TDBListBox 当用户编辑修改表中当前记录的某个字段时,该部件

是一个包含多个选择项的列表框,用户可以从中选择

一个项做为字段的值

────────────────────────────────

TDBComboBOx 该部件是一个组合框,当用户编辑修改表中当前记录

的一个指定字段时,可以直接在该部件中输入字段值

也可以单击该部件从下拉式列表框中选择一个字段值

────────────────────────────────

TDBCheck 用于显示数据库中的字段信息的检查框,当表中字段

的值与该检查框的ValueChecked属性值相匹配时,该

检查框被选中

────────────────────────────────

TDBReadioGroup 使用该部件可以为用户提供一组选择项,但用户只能

从中选择一个可选项

────────────────────────────────

TDBLookapList 当用户要编辑修改数据库表当前记录的指定字段时,

使用该部件提供多个可选项,这多个可选项是从相关

的其他表中读取的,且以列表框的形式提供给用户

────────────────────────────────

TDBLookupCombo 该部件结合了TDBEdit部件和TDBComboBox部件的功能,

用户可以直接向该部件中输入字段值,也可以从下拉

式列表框中选择一个可选项,只是下拉式列表框中的

可选项是从相关的其他的数据库表中读取来的。

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

 

16.1 数据浏览部件的基本特性

 

大多数的数据浏览部件是从标准部件中演变过来的,它们具有一些相似的特性,如TDBGrid部件和TStringGrid部件,TDBEdit和TEdit部件,TDBListBox和TListBox等等,只是数据浏览部件是专门用于显示和编辑数据库中记录的字段信息而已。它们的使用方法以及属性有很大的不同。在程序设计阶段,当数据浏览部件通过TDatasource部件和TTable部件连接到一个物理数据库时,便可在其中观看到数据库中的数据信息,也就是说在程序设计阶段便可以看到应用程序运行之后的效果,这种特性给我们修改应用程序带来了方便。

数据浏览部件是通过TDatasource部件连接到TTable部件中具体的字段部件的,因而数据控制部件具有一些共同的属性,用于连接TDatasource部件和TField部件。

Datasource属性:说明数据浏览部件连接的数据源部件TDatasource,数据浏览部件是从TDatasource部件中获取数据的。

DataField属性:说明数据浏览部件对应数据库表中实际的字段名称。该属性的值其实是TDataSource连接的数据集部件TTable或TQuery部件中的字段部件的名字。

因此要创建一个应用程序显示和编辑数据库表中的记录,一般要在应用窗体中放置一个数据集部件(TTable部件或TQuery部件)和至少一个数据源部件TDataSource部件以及多个数据浏览部件。其创建的一般步骤如下:

 

1.在窗体中放置上述所说的部件并连接数据集部件、数据源部件。

2.为各数据浏览部件设置DataSource属性值为窗休中存在的TDataSource部件的名字。

3.设置各数据浏览部件的DataField属性为数据集部件TTable或TQuery部件中存在的 字段部件的名字。

 

在这里需要注意的是:TDBGrid部件和TDBNavigator部件是自动地访问数据集部件中所有可以访问的字段部件的,因此它们是没有DataField属性的,对于这些部件可以跳过第3步。

Enabled属性:当数据浏览部件连接到数据集部件时,它的Enabled属性决定了数据浏览部件能否接受来自鼠标、键盘和定时器事件的消息。当Enabled属性值为False时,数据浏览部件将变为无效而不能接受外界的信息。当与数据浏览部件相连的TDataSource部件的Enabled属性为False时或与数据源部件TDataSource部件相连的数据集部件TTable或TQuery部件的Active属性为False时,数据浏览部件也会随之而变为无效。

ReadOnly属性:大多数的数据浏览部件能够用来编辑修改与之对应的字段,因而有ReadOnly属性,该属性用来控制是否可以在数据浏览中编辑修改字段的值。缺省情况下,该属性的值为False,也就是说用户可以在其中编辑修改字段的值。

当然用户要想通过数据浏览部件编辑修改数据库表中的记录字段时,还要受到其它因素的制约。除了数据浏览部件本身的ReadOnly属性设置为False外,还要设置其相应的字段部件和数据集部件的CanModify属性True性;设置TDataSource部件的AutoEdit属性为True,如果数据库表是SQL数据库服务器中的数据库表,用户必须要具有读写数据库的权限等。当然在实际的程序设计过程中并没有这样繁琐,因为很多属性的缺省值都是允许用户修改表中的记录的。

除了TDBGrid部件之外,如果通过一个数据浏览部件修改字段值,那么当光标(或焦点)离开数据浏览部件时,数据浏览部件中被修改的值就会自动地被写回到磁盘数据库中。若在焦点没有离开数据浏览部件之前,按ESC键,那么Delphi会自动地放弃其对字段值的修改。在TDBGrid部件中修改表中的记录时,只有当焦点离开当前的记录时,即记录指针移到其他记录上时,用户对当前记录的修改会被写回磁盘上的数据库表,在焦点没有离开当前记录时,按ESC键,Delphi会自动放弃对当前记录的修改。 

16.2 使用TDBText部件显示表中的数据 

TDBText部件是一个只读的数据浏览部件,它类似于TLabel部件。只是TDBText 部件用于显示数据库表中记录的指定字段的值。因为TDBText部件显示的是表中当前记录的指定的字段的值,因而它显示的内容也是动态的,在其中显示的内容随着记录指针的移动而变化。用TDBText部件显示Customer.DB表中的Company字段信息时可用如图16.2所示的窗体来实现。  

其中各部件的属性设置如表16.2所示 

表16.2 表中各部件的属性设置

━━━━━━━━━━━━━━━━━━━

属 性 属 性 值

───────────────────

Table1.DatabaseName DEMOS

Table1.TableName Customer.DB

Datasource1.DataSet Table1

DBText.DataSource DataSource1

DBText.DBField Company

━━━━━━━━━━━━━━━━━━━ 

16.3 使用TDBEdit部件显示和编辑表中的数据 

TDBEdit部件是专门用于显示编辑数据库表中当前记录的各个字段值的数据浏览部件,在应用程序中,我们常常用一个TDBEdit部件来对应表中一个字段,通过设置TDBEdit部件的DataSource、DataField属性便可以为TDBEdit部件指定表中相应的字段。如果用户希望能通过TDBEdit部件编辑修改数据库表中的字段值,还要设置TDBEdit部件的ReadOnly属性为False,设置与TDBEdit相连的数据源部件TdataSource部件的AutoEdit属性为True以及确保与TDataSource部件相连的数据集部件TTable或TQuery部件处于编辑状态,即设置它们的CanModify属性为True。

例如,在图16.3所示的窗体中,使用多个TDBEdit部件显示和编辑Customer.DB表中当前记录的各个字段。窗体中各部件的属性如表16.3所示。 

图16.3 用TDBEdit部件显示和编辑表中的数据 

表16.3 窗体中各部件的属性

━━━━━━━━━━━━━━━━━━━━

属 性 属 性 值

────────────────────

Table1.DatabaseName DEMOS

Table1.TableName Customer.DB

Datasource.DataSet Table1

DataSource.AutoEdit True

DBNavigator.DataSource DataSource1

━━━━━━━━━━━━━━━━━━━━ 

窗体中其它部件都是TLabel部件和TDBEdit部件,TLabel部件用于显示表中各字段的名字,TDBEdit部件对应表中各个字段。程序运行之后如图16.4所示。用户可以在其中任何一个TDBEdit部件中修改其中的字段值。 

用TDBEdit部件显示和修改表中的数据 

窗体中还使用了一个TDBNavigator部件,使用它的目的是在表中移动记录指针,还可以进行修改、插入、删除记录等操作,具体的使用和操作参看 16.5 TDBNaigator部件的使用一节。 

16.4 用TDBGrid部件显示和编辑表中的数据 

TDBGrid部件和TDBEdit部件一样,它们是专门用来显示和编辑数据库表中的数据的,但TDBGrid部件的功能更强大一些,它可以以网格的形式显示数据库表中全部记录的所有字段信息。 

用TDBGrid显示数据库表中的记录信息 


想死你们了!

TOP