数据访问部件的应用及编程

 数据访问部件的应用及编程
转自http://www.newsmth.net/bbsanc.php?path=%2Fgroups%2Fcomp.faq%2FDelphi%2Fjiuban%2F200111%2FDelphiTurtor%2Fdelphi2%2Fc15
    在这一章里我们主要介绍Delphi的数据访问部件的层次结构、 多部件之间的关系、部件的属性、方法、事件以及各部件的应用。这些部件包括:
    ● TSession部件
    ● 数据集部件(TTable和TQuery)
    ● TDatasource部件
    ● 字段对象TField
    ● 字段编辑器的使用
    ● TReport部件和TBatchMove部件
    我们对这些部件的属性、方法和事件进行一般性的描述,读者在实际使用Delphi开发应用程序时,还可以通过联机帮助获得有关部件更详细的信息。

15.1 Delphi数据访问部件的层次结构

    
Delphi提供了强大的开发数据库应用程序的能力,它给用户提供了大量的数据访问部件。以方便程序设计人员开发数据库应用程序。这些部件中,有些部件继承了另一些部件的属性、方法和事件,也就是说多部件之间存在着继承和被继承的关系,各部件的这种关联便构成了一个层次结构,
如图15.1 所示

 

                图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与每一种数据库管理系统进行连接时,都有相应的驱动程序,而且这些驱动程序都可以选择地安装。通过调用GetDriverN
ames方法。我们可以获得当前BDE安装的数据库驱动程序的名字。
    GetDriverParams方法:BDE的数据库驱动程序中包含着多个参数,如支持的民族语言、DBMS的版本号、文件块大小等,对于服务器上的DBMS,还有数据库服务器的名字等等。
    GetTableNames方法:因为每一个数据库都是由多个数据库表组成的,我们通过说明数据库名,然后调用GetTableNames方法,便可以获得该数据库中全部的数据库表的名字。
    上述这些方法在调用时都需要一个字符串列表作为参数, 而且都返回一个字符串列表的值。
    TSession部件还有一个叫DropConnections的方法用于控制应用程序与数据库的连接,当调用DropConnections方法时,应用程序与所有的数据库的连接将会切断。

15.2.3 TSession部件应用举例

    例15.1:我们创建一个应用程序,通过调用TSession有关的方法获取当前应用程序可以进行连接的数据库的名字以及获取其中任意一个数据库中的全部数据库表的名字。创建好的窗体如图15.2所示。

 

              图15.2  通过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 }
                        CheckBox1.Checked, { show extensions flag }
                        CheckBox2.Checked, { show system tables flag }
                        ListBox2.Items);  { target for table list }

      { Make sure there are tables defined in the alias.  If not, show an
        error; otherwise, clear the list box. }

      Screen.Cursor := crDefault;
      if ListBox2.Items.Count < 1 then
        MessageDlg('There are no tables in the alias you selected.  Please ' +
                   'choose another', mtError, [mbOK], 0 );

      ListBox3.Items.Clear;
    end;

    procedure TQueryForm.ListBox2Click(Sender: TObject);
    begin
      Screen.Cursor := crHourglass;
      try
        { First, disable the TTable object. }
        if Table1.Active then
          Table1.Close;

        { Open the selected table }

        with ListBox1 do
          Table1.DatabaseName := Items.Strings[ItemIndex];

        with ListBox2 do
          Table1.TableName := Items.Strings[ItemIndex];

      { Open the table and put a list of the field names in the Fields 
        list box. }

        Table1.Open;
        if Table1.Active then
          Table1.GetFieldNames(ListBox3.Items);
      finally
        Screen.Cursor := crDefault;
      end;
    end;
    end.
    
    程序运行时的窗休如图15.3所示,注意要在该应用的程序单元中的uses语句中加入DB。

 

                  图15.3 通过TSession部件获取数据库的有关信息

15.3  数据集部件TDataSet及其应用

    数据集部件TDataSet是不可见的部件,由它派生出来的两个重要部件是TTable 
部件和TQuery部件,这两个部件有着许多共同的属性、方法和事件,习惯上我们把这两个部件统称为数据集部件。当然存贮过程部件TStoredProc也是一种数据集部件,它是TDBDataset部件派生出来的,但这种数据集部件主要在开发客户/服务器数据库应用程序时使用,有关它的性质及使用
请参考创建客户/服务器应用的章节。在这一节里我们首先介绍TTable部件TQuery部件共同的特性,然后重点介绍TTable部件的特性及应用, 有关Tquery部件的应用我们放到第五章中进行讨论。

15.3.1 数据集部件的几种状态(或称模式)

    因为数据集部件TTable和TQuery直接与磁盘上的实际数据库联系, 这些部件中的数据对应于数据库表中的数据记录。 为了便于应用程序对数据库文件中的数据进行读写操作,Delphi为数据集部件TTable和TQuery设定了几种状态。 数据集部件的状态及其含义如表15.1所示。

                  表15.1  TTable和TQuery的几种状态
    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
        状        态                 含              义
    ──────────────────────────────────
    非活动状态(Inactive)  TTable和TQuery部件对应的表是关闭的
    ──────────────────────────────────
    浏览状态(Browse)      当缺省情况下,TTable和TQery部件对应的表被打开
                          时,是处于该状态,只能浏览表中的数据,不能修
                          改或插入记录。
    ──────────────────────────────────
    编辑状态(Edit)        能够浏览表中的记录而且能够修改当前的记录或删
                          除记录
    ──────────────────────────────────
    插入状态              能够在表中插入一条新记录或删除记录
    ──────────────────────────────────
    查找状态(SetKey)      在该状态下,可以调用FindKey,GotoKey,GotoNear-
                          est,FindNearest方法查找表中的记录,此状态只适
                          用于TTable部件。
    ──────────────────────────────────
    处理计算字段状态      当TTable和TQuery部件中设置了计算字段,并且当部
      (CallFields)        件的OnCalcFields事件处理过程被执行时,Delphi会
                          自动将TTable和TQuery部件置为CallFeilds状态,以
                          防止其它非计算字段的值被修改。
    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

    
TTable部件和TQuery部件的这几种状态,我们可以在应用程序中调用适当的方法来改变或切换,我们调用Insert方法将TTable或TQery置为Insert状态,如Table1.Insert便将Table1设置成了插入状态。同理,调用Edit方法,可以将数据集部件设置在编辑状态。如Table1.Edit便将Table1设
置成编辑状态。数据集部件中的许多方法被调用之后,能自动地把数据集部件置成浏览状态Browse 
。在调用Cancel方法之后,它是总将数据集部件置成Browse状态。CallFields状态是一种比较特殊的状态。这种状态在应用程序中不能够由程序显式地设置,它是在数据集部件的OnCallFields事件发生时自动地设置成这种状态,当数据集部件处于CallFields状态时,用户程序如果试图修改
其他非计算字段的值时,便会触发一个异常错误。当OnCallFields事件被处理完之后,数据集部件的状态会恢复到原来的状态。
    图15.4比较详细地阐述了TTable部件的几种状态在调用相应的方法之后相互转化的关系。
    数据集部件具有一个State属性,该属性的值表示当前数据集所处的状态,相应于数据集的几种状态,State属性可能的属性值有:dsInactive、dsBrowse、dsEdit、dsInsert、dsSetkey、dsCallFields。数据集部件还具有一个OnStateChange事件, 
数据集状态发生变化时,该事件就会被触发。

 

                  图15.4  TTable部件的状态转换图

15.3.2 数据集的打开的关闭

    
数据集部件TTable或TQuery虽然直接与磁盘上的数据库表发生联系,但是应用程序通过数据集部件访问数据库中的数据时,必须把将要被访问的数据库表调入内存,也即将与TTable或TQuery部件相连接的数据库表调入内存,这一操作对数据集部件而言就是打开,反之则称为关闭。Delphi为
我们提供了两种方法打开数据集部件:
    1.通过设置数据集部件的Active属性为True来打开与数据集相连的数据库表。如:

       Table1.Active := true;
       Query1.Active := true;

    这种方法可以在设计阶段进行也可以在应用程序运行过程中通过程序进行。
    2.调用数据集部件的Open方法,打开与数据集部件相连的数据库表。如:

      Table1.Open;
      Query1.Open;

    这一种方法只能在程序运行过程中通过程序进行。同理Delphi 同样提供了两种方法
关闭数据集部件:
    1.设置数据集部件的Active属性为False,以关闭与数据集部件相连的数据库表。

      Table1.Active := False;
      Query1.Active := False;

    2.调用Close方法以关闭与数据集部件相连的表。

        Table1.close;
        Query1.close;


15.3.3 数据集的导航

    数据集中存放着数据库表中的多条数据记录,在我们的应用中,常常需要将记录指针定位到特定的记录上,数据记录的定位过程也称为数据库表的导航。Delphi为方便我们在数据集中定位记录指针,提供了下列方法及属性。

                   表15.2  Delphi的记录定位方法
          ━━━━━━━━━━━━━━━━━━━━━━━
            方法          功            能
          ───────────────────────
           First    移动记录指针到表中的第一条记录
           Next     移动记录指针到表中的下一条记录
           Prior    移动记录指针到表中的前一条记录
           Last     移动记录指针到表中的最后一条记录
          ━━━━━━━━━━━━━━━━━━━━━━━

    
数据集部件中还有两个重要的属性,用于数据集的导航,它们是EOF属性和BOF属性。这两个属性都是布尔型变量,它们用来测试记录指钍。是否位于表中的最后一条或第一条记录。只有当记录指针位于表中最后一条记录时EOF的值才为True。同样,只有当记录指针位于表中第一条记录时BOF
的值才为True。记录指针位于表中的其他记录位置时,EOF和BOF的值都为False,在第一个空表中EOF和BOF的值都为True。
    下面是利用数据集部件的方法和属性在数据集中导航的例子。这一段程序代码提供了一个简单的方法遍历Table1中的全部记录。

    Table1.DisableControls;      {切断Table1与其他数据浏览部件的联系}
    Table1.First;                {将当前指针移到表中的第一条记录}
    While not Table1.EOF  do     {遍历表中的记录,直到最后一条记录
    Begin
      <处理当前的记录>         {对当前的记录进行处理}
       Table1.Next;              {移动当前指针到表中的下一条记录}
    end;
    Table1.enableControls;       {恢复Table1与其他数据浏览部件的联系}

    
Next方法和Proir方法在数据集的导航中一次只能向前、向后移动一步记录指针,Delphi还提供一个函数MoveBy,调用该函数可以向前或向后将记录指针移动到用户指定的地方,也即这一次可以移动多步,MoveBy函数只有一个整数型参数,当参数是正整数时,就是将记录指针向前移动,当
参数是负整数时,将记录指针向后移动。
    例如:在Table1中将记录指针向前移动两条记录的代码为:
          Table1.MoveBy(2);
          在Table1中将记录指针向后移动两条记录的代码为:
          Table1.MoveBy(-2);
    显然  Table1.MoveBy(1)和Table1.Next的作用是一样的;
          Tabel1.MoveBy(-1)和Table1.Prior的作用也是一样的。

15.3.4 数据集中的数据维护

    数据集中的数据维护主要包括数据记录的修改,插入和删除。Delphi为数据集部件提供了相应的方法用于其中的数据维护。这些方法如表15.3所示。



                      表15.3  Delphi用于数据维护的方法
         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
           方  法  名                功              能
         ──────────────────────────────
          Edit         将数据集置为编辑状态
         ──────────────────────────────
          Append       投寄所有被修改的记录,将记录指针移到表中的最后
                       一条记录,且将数据集置为插入状态
         ──────────────────────────────
          Insert       投寄所有被修改的记录将数据集置为插入状态
         ──────────────────────────────
          Post         将插入的新记录和修改的记录写回磁盘上的数据库表,
                       即投寄,当投寄成功时数据集回到浏览状态,若投寄
                       不成功数据集仍然保持原有状态
         ──────────────────────────────
          Cancel       取消当前的操作且将数据集置为浏览状态
         ──────────────────────────────
          Delete       删除当前记录指针所在的记录且将数据集置为浏览状态
         ──────────────────────────────
         AppendRecord  在表的最后插入一条新记录,记录的各个字段值作为
                       AppendRecord的参数传递给新记录
         ──────────────────────────────
         InsertRecord  在当前指针所在记录的后面插入一条新记录, 记录的
                       各个字段值作为InsertRecord的参数传递给新记录。
         ──────────────────────────────
         SetRecords    修改当前记录,字段名和相应的字段值作为SetRecords
                       的参数
         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

    
Edit方法:如果应用程序想对数据集中的数据记录进行修改,我们必须要将数据集设置成编辑状态。调用数据集部件的Edit方法便可以将数据集置成编辑状态,当数据集已经处在编辑状态时,调用Edit方法不会产生作用。当数据集处于编辑状态时,移动记录指针或调用post方法都可以将当
前记录的修改写回到磁盘数据库表中。在程序中, Edit方法和post方法常常配合在一起使用,用于修改表中的记录。如:

    Table1.Edit;
    Tabel1.FieldByName('CustNo').Asstring := '1234';
    Table1.post;

    在上述这一段程序代码中,第一行程序是将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
    ● FreeBookMark

    
这三个方法一般都是在一起使用,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方法,数据浏览部件中显示的信息也随之改变。

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中的字段指定查找值进行查找时,首先设置TTa
ble部件的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        日期时间型字段
       ━━━━━━━━━━━━━━━━━━━━━━━━━━━

    我们在本书中只介绍一些常见类型的字段部件的使用,其他类型字段部件的使用可以参看联机帮助文件。

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属性,打开输入模式编辑器,如图15.6所示。

 

                          图15.6  字段输入模式编辑器

    在字段输入模式编辑中可以选择一种输入模式,而且在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被打开以后,窗体的名字和数据集部件的名字会显示在窗口的标题上,如图15.7所示。




 

                            图15.7  字段编辑器Fields Editor

    图15.7中的Fields列表框是用于显示已经创建的永久性字段部件的名字的。字段编辑器Fields Editor第一次被打开时, 
该列表框是空的,因为在此之前的字段部件都是动态生成的,只要Fields列表框中有字段部件,那么与数据集部件相连的数据浏览部件中只显示Fields中列出的字段的字段值,在Fields列表框中,可以通过拖放字段部件的名字来改变相应的字段值在数据浏览部件中的显示顺序,如在TDBGri
d部件中根据各字段在Fields列表框中的顺序显示各字段的值。
    在字段编辑器Fields Editor窗体上面的导航按钮是用来移动TTable或TQuery部件中的记录指针的,使用导航按钮可以将记录指针向前、向后移动,也可以移到第一条记录处或最后一条记录处。
    单击鼠标右键会弹出一个弹出式菜单如图15.8所示。

 

                    图15.8  字段编辑器中的弹出式菜单




15.6.2.2 增加字段部件

    字段编辑器Fields Editor中的Add Fields菜单项用于向数据集部件中增加字段部件的,单击Add Fields菜单项时便会打开增加字段部件对话框,如图15.9所示。Available 
Fields列表框中显示出数据集部件TTable或TQuery中当前可以用于创建永久字段部件的全部的字段,也就是说Available 
Fields列表框中显示字段是数据库表中实际存在的字段,而且还没有为这些字段创建相应的永久性的字段部件,在缺省状态下所有的字段都被选择用于创建相应的永久性的字段部件,用鼠标单击其中的字段名可以有选择地创建其相应的永久性的字段部件,选择好有关的字段名之后,单击OK
按钮便可以创建永久性的字段部件,创建好的字段部件的名字会显示在图15.7中所示的Fields列表框中。

 

                    图15.9  字段编辑器的增加字段部件对话框

    在图15.9所示的对话框中单击custNo、company字段名,然后单击ok按钮,创建它们对应的永久性的字段部件,如图15.10所示。

 

                            图15.10  创建字段部件

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列表框中。图15.12是创建新的计算字段NewField后字段编辑      器的情形。
 

        图15.11  定义新字段对话框 

 
                          图15.12  创建新的计算字段
    新的计算字段创建好了之后,它是没有任何字段值的,我们必须要编写相应的程序代码,根据数据库表中实际存在的字段的字段值为计算字段的宝定义字段值,我们为计算字段所在数据集部件的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工具。一般情况下,如果应用程序只有一个报表或者只有较少的报表要输出时,应设置AutoUnloa
d属性为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中的DataSou
rce2的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部件,用于记录的导航。 两个窗体如图15.13所示。

 

                     图15.13  两个窗体显示同一个表中的记录

posted on 2012-12-23 20:26  万剑  阅读(458)  评论(0编辑  收藏  举报