可能你不知道的TClientDataset功能=====網上收集

keyLife富翁筆記 
作者: shiningplus
標題: delphi Midas SQLServer的自增字段的處理
關鍵字: delphi Midas 自增字段
分類: 個人專區
密級: 公開
(評分: , 回覆: 0, 閱讀: 1830) ??
delphi Midas SQLServer的自增字段的處理
1.新增時,表中有自增字段,但是不希望用Refresh,直接ApplyUpdates直接看見自增字段的值
在DataSetProvider.AfterUpdateRecord寫如下代碼

DataSetProvider.Options.poPropogateChanges:=True;

procedure TForm1.DataSetProvider1AfterUpdateRecord(Sender: TObject;
  SourceDS: TDataSet; DeltaDS: TCustomClientDataSet;
  UpdateKind: TUpdateKind);
begin
//DstId TADODataset
//FId 爲自增字段
  if   UpdateKind=ukInsert then
  begin
  DstId.CommandText:='select  @@Identity as FId ';
  DstId.Open;
  DeltaDS.FieldByName('FId').ReadOnly:=False;
  DeltaDS.FieldByName('FId').NewValue:=DstId.FieldByName('FId').AsInteger ;
  DstId.Close;
  end;

end;

2.
新增時,從表的關聯字段與主表的自增字段同步更新
procedure TProducts.DataSetProvider1BeforeUpdateRecord(Sender: TObject;
  SourceDS: TDataSet; DeltaDS: TCustomClientDataSet;
  UpdateKind: TUpdateKind; var Applied: Boolean);
begin
//DstProduct爲從表的Name
//CategoryID是從表的對於主表的字增自段的關聯字段
// qryIdentity是TADOQuery qryIdentity.SQL:='select @@identity'

  if (UpdateKind = ukInsert) and
  (SourceDS = DstProduct) and
  (DeltaDS.FieldByName('CategoryID').Value =  Unassigned)   ) then
  begin
    if DeltaDS.BOF then
    begin
      qryIdentity.Close;
      qryIdentity.Open;
    end;
    DeltaDS.FieldByName('CategoryID').NewValue := qryIdentity.Fields[0].Value;
  end;
end;


Delphi做爲一個快速應用開發工具,深受程序員的喜愛。其強大的組件功能,讓程序員能夠輕鬆、高效地完成常見的界面開發、數據庫應用等功能。然而,幫助的相對缺乏,使得許多組件的功能並不爲人們正確地使用,究其原因,仍然是認識上的問題。對於MIDAS開發中的核心部件,TClientDataSet和TDataSetProvider,由於資料的缺乏,人們在網上大多談論的是李維的書籍內容。我有幸在BDN上見到了Cary Jensen的Professional Developer系列文章,詳細闡述了DELPHI的數據庫開發技術。現節選出其中的ClientDataSet部分,與大家共同分享。

ClientDataSet是一個功能強大的類,通過在內存中模擬表格,實現了其它數據集組件所不具備的強大功能。以往只在Delphi和C++ Builder企業版中才提供這個組件,如今,Borland的全部產品(包括最新的Kylix)都集成了TClientDataSet組件。

TClientDataSet從類的繼承關係上來看,是TDataSet這個抽象類的子類,所以我們可以在TDataSet這個抽象層次上對其進行我們熟悉的操作,比如導航、排序、過濾、編輯。要注意的是,TClientDataSet使用了一種全新的技術,它將所有的數據均放在內存中,所以TClientDataSet是個只存在內存中的“虛擬表”,因此對數據庫的操作是非常快的。在PIII 850,512MB的機器上對十萬條記錄進行建索引的操作,花費的時間少於半分鐘。

與一般的數據集組件不同,TClientDataSet使用的技術比較特別,本着高速度、低存儲需求的原則,TClientDataSet的內部使用了兩個數據存儲源。第一個是其Data屬性,這是當前內存數據的視圖,反映了所有的數據改變。如果用戶從數據中刪除一條記錄,則此記錄將從Data中消失,相應地,加入一條新記錄後,此記錄便存在Data屬性中了。

另一個數據源是Delta屬性,故名思義,即增量的意思,這個屬性反映了對數據的改變。無論是向Data屬性新增還是刪除記錄,都會在Delta中記錄下來,如果是修改了Data中的記錄,則會在Delta保存兩條相應的記錄,一條是原始記錄,另一條僅包含修改的字段值。正因爲Delta的存在和TClientDataSet在內存中記錄數據的特點,所有的改變都沒有立即更新加對應的物理存儲中,可以根據這些信息在適當的時候恢復,所以TClientDataSet天生具有緩衝更新功能。

爲了使數據更新回數據存儲源,我們要調用TClientDataSet中對應的方法。如果ClientDataSet與DataSetProvider關聯,那麼僅需調用TClientDataSet的ApplyUpdates方法即可保存數據的更新,但如果TClientDataSet沒有對應的TDataSetProvider存在,而是直接同文件關聯,那麼,這種方式是非常有趣的,我們在BriefCase模型中會再次講解這個問題。此時,如果使用TClientDataSet的SaveToFile和LoadFromFile,都會保留着Delta。調用MergeChangeLog和ClearChanges後,Delta的內容纔會被
清空。只是前者是將Delta的數據同Data結合起來,將改變存儲到物理介質上,而ClearChanges則是一股腦兒全部清空,將數據回覆到原始狀態。大部分的應用都是將TClientDataSet與TDataSetProvider結合使用的。兩者聯合使用的行爲反映了Borland的設計宗旨,就是要提供一個面向分佈式環境的思路。我們下面來慢慢解釋。

當我們將TClientDataSet對象的Active屬性設爲True或者調用其Open方法後,ClientDataSet會向DataSetProvider發送一個取數據包請求。於是DataSetProvider便會打開對應的數據集,將記錄指針指向第一條記錄,然後從頭到尾依次掃描。對於掃描到的每一條記錄,都會將其編碼成一個variant數組,我們通常將它稱之爲數據包。完成掃描後,DataSetProvider會關閉指向的數據集,並將所有的這些數據包傳遞給ClientDataSet。在我提供的演示程序中,你可以清楚地看到這種行爲(畢竟眼見爲實嗎!)。程序主界面右邊的DBGrid連接到一個指向數據庫表的數據源,DataSetProvider即指向此表。當選擇了ClientDataSet | Load菜單項時,你可以看到表格的數據被依次掃描,一旦到達最後一條記錄,表格便會被關閉,右邊的DBGrid被清空,而左邊反映ClientDataSet數據的DBGrid便出顯示出內存中的數據來。由於這個過程會在DBGrid上反映出來,所以不到1000條記錄的取出時間中,大部分都浪費在屏幕的更新顯示上了,你可以選擇ClientDataSet | View Table Loading來禁止顯示,而達到加速的目的。

在上面的描述中,我們沒有提到一個重要的環節,即數據包是如何還原成表格的。那是因爲DataSetProvider會將數據包中的元數據解碼出來,根據元數據(我們可以理解爲數據表的結構)便可以構造出與物理數據表一模一樣的內存虛擬表。但要注意的是,儘管DataSetProvider指向的數據表可能有多個索引,但這些信息是不會放在數據包中的,換句話說,ClientDataSet當中的數據默認情況下是無索引的。但因爲ClientDataSet具有與TDataSet一致的行爲,所以我們可以在此基礎上根據需要重建索引。

在ClientDataSet中的數據被修改後,可以提交給物理數據表持久化這此改變。這個工作便是由DataSetProvider完成的。內部工作原理是:DataSetProvider創建一個TSQLResolver的實例,這個實例會生成要在底層數據上執行更改的SQL語句。詳細地說,就是對修改日誌中的每一條被刪除、插入、更改記錄生成對應的SQL語句。這個語句的生成也可以由用戶控制,DataSetProvider的UpdateMode屬性和ClientDataSet中的ProviderFlags屬性都對SQL語句的生成有影響。

當然,你也可以換一種方式,即採取同單機或C/S結構一樣的數據直接操作機制,繞過SQL語句和緩衝更新機制來修改數據庫。只需將ResolveToDataSet屬性設爲True,那麼DataSetProvider在持久化更新時便不會使用TSQLResolve,而是直接修改物理數據源。即定位到要刪除的記錄,調用刪除語句,定位到修改記錄,調用修改語句。我們可以對演示程序稍加修改,觀察此種行爲。請將演示程序中的DataSetProvider的ResolveToDataSet屬性由False改爲True,運行。在界面中修改數據並且保存,你將會看到右邊的導航按鈕會在瞬間變得可用。

更絕妙的是,Borland考慮到了應用的多樣性,爲我們提供了BeforeUpdateRecord事件,這樣,當DataSetProvider對每個修改日誌的記錄進行操作時,都會觸發此事件,我們可以在此事件中加入自己的處理,如“加密操作”、“商業敏感數據處理”等應用,從而極大地方便了程序員,讓程序員對於數據具有完全的控制能力。分佈式環境的複雜性對數據的存取提出了更高的要求,所以使用事務來保證數據的完整性和一致性是非常必要的,Borland考慮到了這一點,當調用ClientDataSet的ApplyUpdates時,你可以傳遞一個整數值來指明可以容忍的錯誤數量。如果你的數據非常嚴格,則可以傳遞0值,這樣,DataSetProvider在應用修改時便會打開一個事務,如果遇到錯誤,便會回退此事務,修改日誌將保持原樣,並且將出錯的記錄標記出來,最後會觸發OnReconcileError事件。如果傳遞了一個大於0的數,則當出現的錯誤數量小於此指定值時,事務會被提交,發生錯誤而導致提交失敗的記錄會保留在Delta中,而提交成功的記錄會從修改日誌中刪除。若錯誤數量達到指定值,則事務會回退,結果同整數值爲0的情況。如果值爲負數,則會交所以可提交的數據都提交,不可提交的數據仍然保存在修改日誌中,並將出錯記錄標記出來。

雖然,Borland是爲了滿足分佈式編程的需要而設計了TClientDataSet,但在其它類型的編程環境中使用ClientDataSet也具有積極的意義。首先,我們可以看到,由於數據均在內存中進行操作,而且僅在打開數據庫取數據時和將修改持久到回數據庫時,纔有數據庫開銷,其它時間數據庫爲零,這樣就極大地增加了數據庫的負荷,讓數據庫服務器能滿足更多用戶的連接請求。其次,ClientDataSet具有其它數據集所不具備的許多高級功能,這爲程序員進行復雜的編程提供了便利,可以不考慮數據庫本身是否支持這此功能,而讓ClientDataSet去處理這些複雜而繁瑣的細節。最後,ClientDataSet在數據存儲和應用程序間起到一個抽象層的作用。假如你的程序使用了TClientDataSet,那麼如果你以後要更改數據庫存儲機制。比如說由BDE移植到dbExpress,或者從ADO移植到Interbase Express,你的用戶界面和數據控制部分幾乎就不用改變,只需要將DataSetProvider指向新的數據存取組件即可。順便說一句,由於緩衝更新的存在,用戶可能非常厭惡調用ApplyUpdates操作,那麼你可以將此調用放入AfterPost和AfterDelte中,讓用戶的操作更方便。


多層結構中必不可少件TClientDataSet的全面剖析2008-12-11 15:01在三層結構中,TClientDataSet的地位是不可估量的,她的使用正確與否,是十分關鍵的,本文從以下幾個方面闡述她的使用,希望對你有所幫助.
1.動態索引
procedure TForm1.DBGrid1TitleClick(Column: TColumn);
begin
if (not column.Field is Tblobfield) then//Tblobfield不能索引,二進制
ClientDataSet1.IndexFieldNames:=column.Field.FieldName;
end;
2.多層結構中主從表的實現
設主表ClientDataSet1.packetrecord爲-1,所有記錄
設從表ClientDataSet1.packetrecord爲0,當前記錄
3.Taggregates使用
(1)在字段編輯中add new field類型爲aggregates
後設置expression(表達試)
設置active:=true即可
使用dbedit的field爲前者即可
(2)使用Aggergates屬性add設計表達試
調用
  showmessage(floattostr(ClientDataSet1.Aggregates.Count));
  showmessage(ClientDataSet1.Aggregates.Items[0].Value);
 
4.在單層數據庫中不要BDE
使用ClientDataSet代替table,使用ClientDataSet的loadfilename裝入cds
代替table的tablename的db或者dbf
原來的程序改造方法:
加一個ClientDataSet,使用右鍵assign locate data
後savetofile,再loadfromfile,後刪除table
將原連table的datasource設爲ClientDataSet
唯一注意的是:要將midas.dll拷到system或者當前目錄
5.三層結構的公文包的實現方法
同時設定1:filename(*.cds)2.remote server
6.可以對data賦值(從另一個數據集取值)
ClientDataSet2.Data:=ClientDataSet1.Data;
ClientDataSet2.Open;
或者
ClientDataSet2.CloneCursor(ClientDataSet1,true);
ClientDataSet2.Open;
7.附加數據取得
客戶程序嚮應用服務器請求數據。如果TClientDataSet 的
FetchOnDemand 屬性設爲True,
客戶程序會根據需要自動檢索附加的數據包如BLOB字段的值或嵌套表的內容。
否則,
客戶程序需要顯式地調用GetNextPacket 才能獲得這些附加的數據包。
ClientDataSet的packetrecords設置一次取得的記錄個數
8.ClientDataSet與服務器端query連接方法
(1)sql內容爲空
ClientDataSet1.Close;
ClientDataSet1.CommandText:=edit1.Text;//即sql內容
ClientDataSet1.Open;
對於沒有應用服務器設置filter 如:country like 'A%'
filtered=true可實現sql功能
(2)有參數
如服務端query的sql爲
select * from animals
where name like :dd
則:客戶端ClientDataSet
var
pm:Tparam;
begin
ClientDataSet1.Close;
ClientDataSet1.ProviderName:='DataSetProvider1';
pm:=Tparam.Create(nil);
pm.Name:='dd';
pm.DataType:=ftString;
ClientDataSet1.Params.Clear;
ClientDataSet1.Params.AddParam(pm);
ClientDataSet1.Params.ParamByName('dd').AsString:=edit1.Text ;
ClientDataSet1.Open;
pm.Free;
end;

9.數據的更新管理
(1)savepoint 保存目前爲止數據狀態,可以恢復到這個狀態
var
pp:integer;
begin
pp:=ClientDataSet1.SavePoint;
ClientDataSet1.Edit;
ClientDataSet1.FieldByName('姓名').asstring:='古話';
ClientDataSet1.Post;
table1.Refresh;
end;
恢復點
ClientDataSet1.SavePoint:=pp;
(2)cancel,RevertRecord
取消對當前記錄的修改,只適合沒有post的,如果post,調用
RevertRecord
(3)cancelupdate
取消對數據庫所有的修改
(4)UndoLastChange(boolean),changecount
取消上一次的修改,可以實現連續撤消
參數爲true:光標到恢復處
false:光標在當前位置不動
changecount返回修改記錄的次數,一個記錄修改多次,返回只一次
但UndoLastChange只撤消一次

10.可寫的recno
對於Ttable和Tquery的recno是隻讀的,而TClientDataSet的recno可讀可寫
ClientDataSet1.recno:=5;是設第五個記錄爲當前記錄
11.數據保存
對於table使用post可更新數據
而ClientDataSet1的post只更新內存數據,要更新服務器數據要使用
ApplyUpdates(MaxErrors: Integer),他有一個參數,是允許發出錯誤的
次數,-1表示無數次,使用simpleobjectbroker時常設爲0,實現自動容錯和負載平衡

ClientDataSet排序
1、簡單排序
 ClientDataSet1.IndexFieldNames:='排序字段'
2、複雜排序(建立索引)
下面這個過程僅供參考(因爲用到三方控件DBGridEh):
procedure TDM1.DsSort(SortColumn: TColumnEh);
var
 OldIndex:string;
begin
 if (SortColumn.Grid.DataSource=nil) or (SortColumn.Grid.DataSource.DataSet=nil) or (not SortColumn.Grid.DataSource.DataSet.Active) then Exit;
 OldIndex:=TClientDataSet(SortColumn.Field.DataSet).IndexName;
 if OldIndex<>'' then
 begin
   TClientDataSet(SortColumn.Field.DataSet).IndexName:='';
   TClientDataSet(SortColumn.Field.DataSet).DeleteIndex(OldIndex);
 end;
 case SortColumn.Title.SortMarker of
   smNoneEh,
   smUpEh  :TClientDataSet(SortColumn.Field.DataSet).AddIndex('px',SortColumn.Field.FieldName,[ixDescending]);
   smDownEh:TClientDataSet(SortColumn.Field.DataSet).AddIndex('px',SortColumn.Field.FieldName,[ixPrimary]);
 end;
 TClientDataSet(SortColumn.Field.DataSet).IndexName:='px';
end;  把上面的過程稍做修改,可用於標準DBGridvar
  ASC:Boolean=True;//是否升序排列
procedure TDM1.DsSort(SortColumn: TColumn);
var
  OldIndex:string;
begin
if (SortColumn.Grid.DataSource=nil) or (SortColumn.Grid.DataSource.DataSet=nil) or (not SortColumn.Grid.DataSource.DataSet.Active) then Exit;
OldIndex:=TClientDataSet(SortColumn.Field.DataSet).IndexName;
if OldIndex<>'' then
begin
  TClientDataSet(SortColumn.Field.DataSet).IndexName:='';
  TClientDataSet(SortColumn.Field.DataSet).DeleteIndex(OldIndex);
end;
case ASC of
  True :TClientDataSet(SortColumn.Field.DataSet).AddIndex('px',SortColumn.Field.FieldName,[ixDescending]);//已經是升序就按降序排列
  else//否則按升序排列
  TClientDataSet(SortColumn.Field.DataSet).AddIndex('px',SortColumn.Field.FieldName,[ixPrimary]);
end;{end case}
TClientDataSet(SortColumn.Field.DataSet).IndexName:='px';
ASC:=not ASC;
end;
用於TABLEvar
  ASC:Boolean=True;//是否升序排列


procedure DsSort(SortColumn: TColumn);
var
  OldIndex:string;
begin
  if (SortColumn.Grid.DataSource=nil) or
     (SortColumn.Grid.DataSource.DataSet=nil) or
     (not SortColumn.Grid.DataSource.DataSet.Active) then Exit;
  OldIndex:=TTable(SortColumn.Field.DataSet).IndexName;
  if OldIndex<>'' then
  begin
    TTable(SortColumn.Field.DataSet).IndexName:='';
    TTable(SortColumn.Field.DataSet).DeleteIndex(OldIndex);
  end;

  try
  TTable(SortColumn.Field.DataSet).DeleteIndex('px');
  except
  end;

  case ASC of
    True : TTable(SortColumn.Field.DataSet).AddIndex('px',
                  SortColumn.Field.FieldName,
                  [ixDescending]);//已經是升序就按降序排列
    else//否則按升序排列
    TTable(SortColumn.Field.DataSet).AddIndex('px',
           SortColumn.Field.FieldName,
           [ixPrimary]);
  end;{end case}

  TTable(SortColumn.Field.DataSet).IndexName:='px';
  ASC:=not ASC;
end;
//當點擊DBGRID標題時調用
procedure TForm1.DBGrid1TitleClick(Column: TColumn);
begin
  DsSort(Column);
end;

ClientDataSet的隱含功能------轉載《Delphi 從入門到精通》

                      可能與前面的筆記有重複的地方


    ClientDataSet組件支持很多特性,其中一些與三級結構有關,而且還可以用在其他環境中。該組件說明了一個數據庫完全映象在內存中,這使得可以進行動態的操作,如建立一個索引,其他數據集合通常不支持該特性。例如,爲了對查詢分類,我們通常是重新執行它。爲了索引一個局部表格,需要定義索引。只有ADO數據集合有一些與ClientDataSet一樣的動態索引功能。
    索引並不是ClientDataSet提供的全部功能。當我們擁有了索引之後,可以基於它定義組,可能是多級別的分組。對於確定一個記錄在組中的位置(頭、尾或中間位置),甚至有專門的支持。在組或整個數據表格中,我們可以定義總計;也就是說,可以動態計算整個表格或當前組中一列的總和或平均值。數據不需要發送給物理服務器,因爲這些總計操作發生在內存中。我們甚至可以定義新的總計字段,可以直接與數據敏感控件相連。
    注意,所有這些特性不但可以用與MIDAS應用程序,還可以用與客戶機/服務器,甚至是局部瘦應用程序。事實上,ClientDataSet組件可以從遠程MIDAS連接、局部數據集合(建立起數據的快照)、或局部文件(就象在公文包模式中一樣,但使用的只是在客戶機數據集合中定義的整個表格)中獲得起數據。
    這是另一個需要研究的領域,所以將向讀者演示兩個範例來突出關鍵特性。這些範例沒有基於MIDAS,而是基於局部表格。

1、定義抽象的數據的數據類型

    VCL數據庫支持的一個有趣的特性是,當我們基於局部文件使用ClientDataSet時,可以定義抽象的數據類型。只需在窗體上放置一個ClientDataSet組件,爲FieldDefs屬性激活編輯器,添加兩個字段,併爲他們的DataType屬性選擇ftADT值。現在,移到ChildDefs屬性,並定義子字段,下面是AdtDemo範例的字段定義:
    FieldDefs = <
      item
        Name = 'ID'
        DataType = ftInteger
      end
      item
        name = 'Name'
        ChildDefs = <
          item
            name = 'LastName'
            DataType = ftString
            size = 20
          end
          item
            name = 'FirstName'
            datatype = ftString
            size = 20
          end>
       datatype = ftADT
       size = 2
     end>

    在此,只需爲ClientDataSet的FileName屬性輸入一個名稱,用鼠標右鍵單擊組件,並選擇Create Table命令即可;我們準備編譯並運行應用程序(在向它連接數據敏感組件之後)。數據會自動從提供的文件中讀取,關閉程序時會將變化保存在文件中。
    如果使用DBGrid查看結果數據集合,它允許我們展開或壓縮ADT字段的子字段。我們可以通過定義字段的OnGetText事件提供它的壓縮值(在Delphi4 中有一個缺省值,但Delphi5中沒有):
procedure TForm1.ClientDataSet1NameGetText(Sender:TField;
  var Text:String;DisplayText:Boolean);
begin
  Text:=ClientDataSet1NameFirstName.AsString+' '+
        ClientDataSet1NameLastName.AsString;
end;

2、動態索引

    一旦ClientDataSet上有了數據,數據就已全部處於內存中了。當我們將組件基於局部文件中時(如在AdtDemo範例中),在程序啓動時整個文件就被裝載到了內存總。這與從Paradox數據表格中裝載數據(BDE只裝載正訪問的字段)不同。
    將整個表格裝在內存中的優點是,我們可以快速地對它進行分類。使用ClientDataSet組件,我們可以通過賦給IndexFieldNames屬性相應的字段名來實現分類。在AdtDemo(以及很多程序)中,該索引變動會在單擊DBGrid控件的標題(觸發OnTitleClick事件)時執行:

procedure TForm1.DBGrid1TitleClick(Column:TColumn);
begin
  if Column.Field.FullName = 'Name' then
     ClientDataSet1.IndexFieldNames := 'Name.LastName'
  else
     ClientDataSet1.IndexFieldNames := Column.Field.FullName;
end;

   由於ADT定義,程序使用了字段的FullName屬性(而不是FieldName屬性)。事實上,對於子字段來說,索引應該基於Name.LastName,而不是LastName。而且ADT字段不能自己被索引,所以如果選擇它,程序會使用LastName子字段作爲索引。這些索引不是持久性的;它們沒有保存在文件中,而只是在內存中應用於數據。

   技巧:ClientDataSet可以擁有基於計算字段的索引,特別是內部計算字段,這種字段類型只能用於該數據集合。

3、分組

   一旦爲ClientDataSet定義了一個索引,就可以通過該索引對數據進行分組了。實際上,一組被定義爲連續記錄的一個列表(根據索引),記錄中被索引的字段的值不會改變。例如,如果有一個基於國家的索引,帶有該國家的所有地址都將歸爲一組。
   cdsCalcs範例有一個ClientDataSet組件,它同樣從DBDEMOS數據庫的Country表格中讀取其數據。該操作可以在設計時,使用ClientDataSet組件快捷菜單的Assign Local Data命令來執行。爲了在運行時讀取數據,獲得一個更新的快照,可以向窗體添加一個DataSetProvider組件,如下連接三個組件:
   Object Table :TTable
     active = true
     databasename = 'dbdemos'
     tablename = 'country.db'
   end
   object datasetprovider1: TDataSetProvider
     dataset = table1
   end
   object clientdataset1: tclientdataset
     providername = 'datasetprovider1'
   end
現在我們來看看組的定義。該定義可以通過爲索引指定一個分組級別,與索引定義一起獲得:
   object clientdataset1: tclientdataset
     indexdefs = <
       item
         name = 'clientdataset1index1'
         fields = 'continent'
         groupinglevel = 1
       end>
     indexname = 'clientdtaset1index1'
當擁有了一組之後,我們可以在DBGrid中向用戶顯示分組結構。只需爲分組字段(在範例中是Continent字段)處理OnGetText事件,只有當記錄是組的第一個記錄是才顯示文本:
procedure TForm1.ClientDataSet1ContinentGetText(Sender:TField;
  var Text:String;DisplayText:Boolean);
begin
  if gbFirst in ClientDataSet1.GetGroupState(1) then
     Text:= sender.asstring
  else
     text:='';
end;

4、定義合計

   ClientDataSet組件另一個功能強大的特性是對合計的支持。合計是一個基於多個記錄的計算值,如整個數據表格或一組記錄(使用我們剛纔討論過的分組邏輯來定義)中某個字段的和值或平均值。合計是可持續的;也就是說,如果有一個記錄發生改變,會立刻重新計算合計值。例如,當擁護在發貨清單條目中輸入時,發貨單的總和會自動被重新計算出來。
   注意:::合計是遞增維持的,而不是每當有一個值改動時就重新計算所有的值。合計的更新利用了ClientDataSet追蹤的Delta。例如,當字段發生改變時,爲了更新Sum,ClientDataSet會從合計中讀取舊值,並加上新值。只需要兩次計算,即使在該合計組中有上千行。因此,合計更新是瞬時的。
   有兩種方法定義合計。我們可以使用ClientDataSet(是一個集合)的Aggregates屬性,或可以使用Fields編輯器定義合計字段。在這兩種情況下,我們定義的合計表達式,賦給它一個名稱,並將它與一個索引和一個分組級別(除非想將它應用於整個數據表格)連接。下面是CdsCalcs範例的Aggregates集合:
Object ClientDataSet1: TClientDataSet
  Aggregates = <
    item
      Active = True
      AggregateName = 'Count'
      Expression = 'Count(Name)'
      GroupingLevel = 1
      IndexName = 'ClientDataSet1Index1'
      Visible = False
    end
    item
      Active = True
      AggregateName = 'TotalPopulation'
      Expression = 'SUM(POPULATION)'
      Visible = False
    end>
  AggregatesActive = True
注意,在上面的最後一行代碼中,除了激活每個想使用的特定合計之外,我們還必須爲合計激活支持。解除合計是重要的,因爲合計太多會減慢程序執行的速度。我們曾提到的另一種方法是使用Fields編輯器,在其快捷菜單中選擇New Field命令,並選擇Aggregate選項(只有在一個ClientDataSet中可以與InternalCalc選項一起使用)。下面是一個合計字段的定義:
Object ClientDataSet1: TClientDataSet
  object ClientDataSet1TotalArea: TAggregateField
    FieldName = 'TotalArea'
    ReadOnly = True
    Visible = True
    Active = True
    DisplayFormat = '###,###,###'
    Expression = 'SUM(AREA)'
    GroupingLevel = 1
    IndexName = 'ClientDataSet1Index1'
  end
    合計字段在Fields編輯器中被顯示爲獨立的一組。與普通合計相比,使用合計字段的優點是,我們可以定義顯示格式,並將字段直接與數據敏感控件相連,如CdsCalcs範例中的DBEdit。因爲合計與一個組相連,所以要選擇了另一組的記錄,輸出就會被自動更新。而且,如果改變數據,合計值也會立刻顯示新值。
    爲了使用普通合計,必須編寫一些代碼,如下例子中所示(注意合計的Value是一個變體):
procedure TForm1.Button1Click(Sender: TObject);
begin
  Label1.Caption:= 'Area : '+ ClientDataSet1TotalArea.DisplayText +
  #13'Population : ' + FormatFloat('###,###,###',ClientDataSet1.Aggregates[1].Value)
  + #13'Number : ' + IntToStr(ClientDataSet1.Aggregates[0].Value);
end;

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章