TClientDataSet的基本屬性和方法
TClientDataSet控件繼承自TDataSet,其數據存儲文件格式擴展名為 .cds/.xml,是基於文件型數據存儲和操作的控件。
該控件封裝了對數據進行操作處理的接口和功能,而本身並不依賴其它數據庫驅動程序,基本上能滿足單機"瘦"數據庫應用程序的需要。
FieldDefs: 字段定義列表屬性
可通過單擊屬性編輯器中的屬性編輯按鈕,或在該控件上單擊右鍵選擇彈出菜單中的"Fields Editor"菜單進行字段編輯。設置完此屬性后,實際上就相當於定義了表的結構;
如果想裝入已有的數據表的結構和數據,可通過單擊右鍵選擇彈出菜單中的"Assign Local Data"菜單,從彈出對話框中選取當前窗體中已與數據庫連接好的數據集控件名稱即可(當前窗體中必須已放置好要套用的數據集控件並打開激活)
使用注意:對於自定義的字段名表,該屬性編輯完后,該控件仍然無法打開。必須右鍵單擊該控件,選擇彈出菜單中的"Create DataSet"菜單,讓該控件以上述編輯的字段列表為依據,創建數據集后,才能夠被激活打開和使用。否則,會出現類似"ClientDataSet1: Missing data provider or data packet."的錯誤(包括在運行期,運行期可調用該控件的CreateDataSet方法,從而動態定義字段和表)
FileName:數據存儲文件的名稱
因該控件是基於文件型的數據操作控件,因此,必須指定所操作的數據文件名稱(默認擴展名稱.cds),從而打開和激活該控件,進而進行數據編輯。
例如:利用此屬性打開指定的.cds文件

var Path: string; begin Path := ExtractFilePath(Application.ExeName); //取得可執行文件路徑 CDataSet1.FileName := Path + 'test.cds'; CDataSet1.Open; end;
CreateDataSet:以FieldDefs中的字段名表為結構建立數據集,常用來進行動態定義表。
例如:動態創建一具有姓名和年齡兩個字段的數據集

//創建字段名表 CDataSet.FieldDefs.Clear; with CDataSet.FieldDefs.AddFieldDef do begin Name := 'Name'; Size := 10; DataType := ftString; end; with CDataSet.FieldDefs.AddFieldDef do begin Name := 'Age'; DataType := ftInteger; end; //動態創建數據集 CDataSet.CreateDataSet; //激活和打開該數據集 CDataSet.Open;
Open:打開和激活數據集控件,從而進行數據編輯
- 如果指定了FileName屬性,則直接用Open方法即可打開和激活該控件
- 如果未指定FileName屬性,可用動態創建和打開數據集,進而操作數據
LoadFromFile/SaveToFile:從文件中裝入表結構和數據以及存儲數據到文件
例如::將數據集的數據存儲到指定文件中

CDataSet.SaveToFile('c:\windows\desktop\test.cds');
Filter, Filtered: 過濾篩選屬性
說明:用於篩選指定條件的記錄,用法同一般數據集控件,略。
例如:在已經激活打開的數據集中篩選性別為男性的記錄

CDataSet.Close; CDataSet.Filter := '性別=''' + '男' + ''''; CDataSet.Filtered := True; CDataSet.Open;
其它方法
First(到首),Prior(向前),Next(向后),Last(到尾),Edit(編輯),CanCel(取消編輯),Post(保存),Insert(插入記錄),Append(添加記錄),Delete(刪除),Refresh(數據刷新)等
說明:
- 當指定了FileName屬性時,其Post方法可將數據存入指定的文件中,類似其SaveToFile方法;
- 如果未指定存儲文件名,則Post方法只將數據存儲在RAM中。
- 其它方法,同一般數據集控件使用方法
使用TClientDataSet控件的應用程序發布的注意事項
使用TClientDataSet控件的程序發布時不需要任何數據庫驅動程序,大大節省了安裝文件的大小。
但是,在發布程序時別忘了將Windows系統目錄下midas.dll與應用程序一起發布,否則,程序可能無法正常運行。
手動建立數據集
示例代碼:

//放置控件: ClientDataSet1、DataSource1、DBGrid1、Button1, 然后 procedure TForm1.Button1Click(Sender: TObject); begin { 添加字段 } with ClientDataSet1.FieldDefs.AddFieldDef do begin Name := 'ID'; DataType := ftInteger; end; with ClientDataSet1.FieldDefs.AddFieldDef do begin Name := 'Name'; DataType := ftString; Size := 12; { ftString 類型的 Size 默認 20 } end; with ClientDataSet1.FieldDefs.AddFieldDef do begin Name := 'Age'; DataType := ftWord; end; with ClientDataSet1.FieldDefs.AddFieldDef do begin Name := 'Sex'; DataType := ftBoolean; end; { 構建數據集, 不可缺少的一步 } ClientDataSet1.CreateDataSet; { 顯示; 如果在設計時已掛接或不需要顯示, 可省略下兩行 } DataSource1.DataSet := ClientDataSet1; DBGrid1.DataSource := DataSource1; { 添加數據 } ClientDataSet1.AppendRecord([1, '張三', 33, True]); ClientDataSet1.AppendRecord([2, '李四', 44, False]); ClientDataSet1.AppendRecord([3, '王五', 55, True]); { 保存為 cds 或 XML } ClientDataSet1.SaveToFile('C:\Temp\TestBinary.cds'); ClientDataSet1.SaveToFile('C:\Temp\TestXMLUTF8.xml', dfXMLUTF8); end;
代碼可另寫為(下面這種方法簡單, 但上一種方法可設置更多選項):

procedure TForm1.Button1Click(Sender: TObject); begin { 添加字段 } with ClientDataSet1.FieldDefs do begin Add('ID', ftInteger); Add('Name', ftString, 12); Add('Age', ftWord); Add('Sex', ftBoolean); end; { 構建數據集, 不可缺少的一步 } ClientDataSet1.CreateDataSet; { 顯示; 如果在設計時已掛接或不需要顯示, 可省略下兩行 } DataSource1.DataSet := ClientDataSet1; DBGrid1.DataSource := DataSource1; { 插入數據 } ClientDataSet1.InsertRecord([1, '張三', 33, True]); ClientDataSet1.InsertRecord([2, '李四', 44, False]); ClientDataSet1.InsertRecord([3, '王五', 55, True]); { 保存為 cds 或 XML } ClientDataSet1.SaveToFile('C:\Temp\TestBinary.cds'); ClientDataSet1.SaveToFile('C:\Temp\TestXMLUTF8.xml', dfXMLUTF8); end;
數據讀取
方法介紹

TClientDataSet.Fields[]; { 字段集合; 它比 FieldList 有更多功能, 如可獲取嵌套字段 } TClientDataSet.FieldList[]; { 字段列表; 它比 Fields 輕便, 如果只是取值用它快一些 } TClientDataSet.FieldByName(); { 根據字段名稱獲取字段對象; 獲取一個字段對象時它比上兩個快 } TClientDataSet.FindField(); { 根據字段名稱查找字段對象 } TClientDataSet.FieldValues[]; { 根據字段名稱獲取字段值; 如果僅是獲取字段值, 這個最快 } TClientDataSet.First; { 到第一個記錄 } TClientDataSet.Next; { 到下一個記錄 } TClientDataSet.Last; { 到最后一個記錄 } TClientDataSet.Prior; { 到上一個記錄 } TClientDataSet.RecNo; { 設置或讀取當前記錄的位置 } TClientDataSet.Bof; { 當前位置是否是第一個記錄 } TClientDataSet.Eof; { 當前位置是否是最后一個記錄 } TClientDataSet.RecordSize; { 一個記錄的大小; 所謂一個記錄就是當前行的所有字段 } TClientDataSet.RecordCount; { 記錄總數; 也就是總行數 } TClientDataSet.GetFieldList(); { 根據指定的幾個字段名獲取字段對象的列表 } TClientDataSet.GetFieldData(); { 把指定字段的值寫入一個緩沖區 } TClientDataSet.GetCurrentRecord(); { 把當前記錄(不包括 Bolb 字段)寫入到一個緩沖區 }
- 讀取字段的結構信息可以使用 TFieldDef 對象(一般來源於 FieldDefs 或 FieldDefList);
- 現在要讀取其中的數據, 應該使用 TField 對象(一般來源於 Fields 或 FieldList).
- Fields[0]、Fields[1] ... Fields[n] 獲取的是當前行的第幾個字段, 可用 Next、RecNo 等指定當前位置(行).
數據讀取示例:
例子使用了 Common Files\CodeGear Shared\Data\holdings.xml, 若更換文件需調整代碼
這是 holdings.xml 的字段信息

ACCT_NBR { 類型是 r8, 對應 ftFloat, 相當於 Double } SYMBOL { 類型是 string, 對應 ftString, 相當於 AnsiString; 指定 Size=7, 加上空結束, 大小是 8 } SHARES { 類型是 r8, 對應 ftFloat, 相當於 Double } PUR_PRICE { 類型是 r8, 對應 ftFloat, 相當於 Double } PUR_DATE { 類型是 date, 對應 ftInteger, 相當於 Integer }
先窗體上放置 ClientDataSet1、DataSource1、DBGrid1、Memo1 和七個 Button

{ 准備數據, 也可在設計時完成 } procedure TForm1.FormCreate(Sender: TObject); begin ChDir(GetEnvironmentVariable('CommonProgramFiles') + '\CodeGear Shared\Data\'); ClientDataSet1.LoadFromFile('holdings.xml'); DBGrid1.DataSource := DataSource1; DataSource1.DataSet := ClientDataSet1; end; { 讀取字段值的幾種方法 } procedure TForm1.Button1Click(Sender: TObject); var v1,v2,v3,v4,v5,v6,v7,v8,v9: Variant; num: Double; fName: string; begin { 獲取首字段的名稱 } fName := ClientDataSet1.Fields[0].FieldName; { 獲取第一個字段值的幾種方法: } v1 := ClientDataSet1.Fields[0].Value; v2 := ClientDataSet1.FieldByName(fName).Value; v3 := ClientDataSet1.FindField(fName).Value; v4 := ClientDataSet1.FieldValues[fName]; v5 := ClientDataSet1[fName]; { FieldValues 是默認的數組屬性 } v6 := ClientDataSet1.FieldList[0].Value; v7 := ClientDataSet1.FieldList.FieldByName(fName).Value; v8 := ClientDataSet1.FieldList.Find(fName).Value; v9 := ClientDataSet1.FieldList.Fields[0].Value; { 已知這個字段是 Double 類型的, 可同時轉換 } num := ClientDataSet1.Fields[0].AsFloat; { 查看結果 } with Memo1.Lines do begin Clear; Add(v1); Add(v2); Add(v3); Add(v4); Add(v5); Add(v6); Add(v7); Add(v8); Add(FloatToStr(num)); end; end; { 遍歷當前行字段的幾種方法 } procedure TForm1.Button2Click(Sender: TObject); var Field: TField; i: Integer; begin Memo1.Clear; for Field in ClientDataSet1.Fields do begin Memo1.Lines.Add(Field.Value); end; Memo1.Lines.Add(''); for i := 0 to ClientDataSet1.FieldCount - 1 do begin Memo1.Lines.Add(ClientDataSet1.Fields[i].Value); end; Memo1.Lines.Add(''); for i := 0 to ClientDataSet1.FieldList.Count - 1 do begin Memo1.Lines.Add(ClientDataSet1.FieldList[i].Value); end; Memo1.Lines.Add(''); end; { First、Next、Last、Prior、RecNo } procedure TForm1.Button3Click(Sender: TObject); var s1,s2,s3: string; begin { 讀取第二行第二個字段 } ClientDataSet1.First; ClientDataSet1.Next; s1 := ClientDataSet1.Fields[1].AsString; { 讀取倒數第二行第二個字段 } ClientDataSet1.Last; ClientDataSet1.Prior; s2 := ClientDataSet1.Fields[1].AsString; { 讀取第四行第二個字段 } ClientDataSet1.RecNo := 4; s3 := ClientDataSet1.Fields[1].AsString; { 查看結果 } with Memo1.Lines do begin Clear; Add('第二行第二個字段: ' + s1); Add('倒數第二行第二個字段: ' + s2); Add('第四行第二個字段: ' + s3); end; end; { 遍歷指定字段的所有記錄 } procedure TForm1.Button4Click(Sender: TObject); var i: Integer; begin if not ClientDataSet1.Bof then ClientDataSet1.First; Memo1.Clear; while not ClientDataSet1.Eof do begin Memo1.Lines.Add(ClientDataSet1.FieldList[0].Value); ClientDataSet1.Next; end; Memo1.Lines.Add('-------'); for i := 1 to ClientDataSet1.RecordCount do begin ClientDataSet1.RecNo := i; Memo1.Lines.Add(ClientDataSet1.FieldList[1].Value); end; end; { 通過 GetFieldList 可以讀取幾個指定字段的 TField 對象的列表 } procedure TForm1.Button5Click(Sender: TObject); var List: TList; Field: TField; i: Integer; begin List := TList.Create; ClientDataSet1.GetFieldList(List, 'ACCT_NBR; SYMBOL; SHARES'); Memo1.Clear; for i := 0 to List.Count - 1 do begin Field := List[i]; Memo1.Lines.Add(Field.Value); end; List.Free; end; { GetFieldData 讀取字段值到指針 } procedure TForm1.Button6Click(Sender: TObject); var F1: Double; F2: array[0..7] of AnsiChar; begin ClientDataSet1.GetFieldData(ClientDataSet1.Fields[0], @F1); ClientDataSet1.GetFieldData(ClientDataSet1.Fields[1], @F2); with Memo1.Lines do begin Clear; Add(FloatToStr(F1)); Add(F2); end; end; //這是后面的例子用到的函數, 轉換 TClientDataSet 時間格式到 TDateTime function TDateTimeRecToDateTime(DataType: TFieldType; Data: TDateTimeRec): TDateTime; var TimeStamp: TTimeStamp; begin case DataType of ftDate: begin TimeStamp.Time := 0; TimeStamp.Date := Data.Date; end; ftTime: begin TimeStamp.Time := Data.Time; TimeStamp.Date := DateDelta; end; else try TimeStamp := MSecsToTimeStamp(Data.DateTime); except TimeStamp.Time := 0; TimeStamp.Date := 0; end; end; Result := TimeStampToDateTime(TimeStamp); end; { GetCurrentRecord 是把當前行的所有字段(不包括 Blob 字段)讀入到緩沖區 } procedure TForm1.Button7Click(Sender: TObject); type THoldingsStruct = packed record { 這是根據 holdings.xml 建立的數據結構 } ACCT_NBR: Double; SYMBOL: array[0..7] of AnsiChar; { 其 Size=7, 但后面還有個 #0 } SHARES: Double; PUR_PRICE: Double; PUR_DATE: Integer; // Other: array[0..4] of Byte; { 它后面還若干字節偏移, 測試時其字節數等於前面的字段數 } end; var buf: THoldingsStruct; DateTimeRec: TDateTimeRec; begin //ShowMessage(IntToStr(ClientDataSet1.RecordSize)); { 可通過這個值對照上面的結構 } if ClientDataSet1.GetCurrentRecord(@buf) then with Memo1.Lines do begin Clear; Add(FloatToStr(buf.ACCT_NBR)); Add(buf.SYMBOL); Add(FloatToStr(buf.SHARES)); Add(FloatToStr(buf.PUR_PRICE)); DateTimeRec.Date := buf.PUR_DATE; Add(DateToStr(TDateTimeRecToDateTime(ftDate, DateTimeRec))); end; end;
數據查找
方法介紹
- Locate: 根據字段列表和對應的字段值查找並定位, 找到返回 True.
- Lookup: 根據字段列表和對應的字段值查找, 返回需要的字段值.
- SetKey、GotoKey 或 SetKey、GotoNearest: 根據索引字段的值查找, 先切換狀態再根據條件定位.
- FindKey 或 FindNearest: 根據索引字段的值查找.
其中的 GotoNearest、FindNearest 在找不到的情況下會定位到近似值
測試代碼:

//准備: 窗體上放一個 ClientDataSet1 和六個 Button { 准備測試數據 } procedure TForm1.FormCreate(Sender: TObject); begin with ClientDataSet1 do begin FieldDefs.Add('ID', ftInteger); FieldDefs.Add('Name', ftString, 6); FieldDefs.Add('Age', ftWord); CreateDataSet; AppendRecord([1, '趙AB', 11]); AppendRecord([2, '錢AB', 22]); AppendRecord([3, '孫AB', 33]); AppendRecord([4, '李AB', 44]); AppendRecord([5, '趙ab', 55]); AppendRecord([6, '錢ab', 66]); AppendRecord([7, '孫ab', 77]); AppendRecord([8, '李ab', 88]); end; end; { Locate 測試 } procedure TForm1.Button1Click(Sender: TObject); begin if ClientDataSet1.Locate('Name', '趙ab', []) then ShowMessage(ClientDataSet1.FieldValues['Age']); { 55 } if ClientDataSet1.Locate('Name', '趙ab', [loCaseInsensitive]) then ShowMessage(ClientDataSet1.FieldValues['Age']); { 11 } if ClientDataSet1.Locate('Name', '錢a', [loPartialKey]) then ShowMessage(ClientDataSet1.FieldValues['Age']); { 66 } if ClientDataSet1.Locate('Name', '錢a', [loCaseInsensitive,loPartialKey]) then ShowMessage(ClientDataSet1.FieldValues['Age']); { 22 } if ClientDataSet1.Locate('Name;Age', VarArrayOf(['錢ab',66]), []) then ShowMessage(ClientDataSet1.FieldValues['Age']); { 66 } end; { Lookup 測試 } procedure TForm1.Button2Click(Sender: TObject); var R: Variant; i: Integer; begin R := ClientDataSet1.Lookup('Name', '錢AB', 'Age'); if not VarIsNull(R) then ShowMessage(R); { 22 } R := ClientDataSet1.Lookup('Name;Age', VarArrayOf(['錢ab',66]), 'Age'); if not VarIsNull(R) then ShowMessage(R); { 66 } R := ClientDataSet1.Lookup('ID', 6, 'Name;Age'); if VarIsArray(R) then for i := VarArrayLowBound(R, 1) to VarArrayHighBound(R, 1) do ShowMessage(R[i]); { 錢ab / 66} end; { SetKey、GotoKey 測試 } procedure TForm1.Button3Click(Sender: TObject); begin ClientDataSet1.IndexFieldNames := 'Name'; ClientDataSet1.SetKey; ClientDataSet1.FieldValues['Name'] := '錢ab'; if ClientDataSet1.GotoKey then ShowMessage(ClientDataSet1.FieldValues['Age']); { 66 } end; { SetKey、GotoNearest 測試 } procedure TForm1.Button4Click(Sender: TObject); begin ClientDataSet1.IndexFieldNames := 'Name'; ClientDataSet1.SetKey; ClientDataSet1.FieldValues['Name'] := '孫'; ClientDataSet1.GotoNearest; ShowMessage(ClientDataSet1.FieldValues['Age']); { 77 } end; { FindKey 測試 } procedure TForm1.Button5Click(Sender: TObject); begin ClientDataSet1.IndexFieldNames := 'Name; Age'; if ClientDataSet1.FindKey(['趙ab']) then ShowMessage(ClientDataSet1.FieldValues['Age']); { 55 } if ClientDataSet1.FindKey(['趙AB', 11]) then ShowMessage(ClientDataSet1.FieldValues['Age']); { 11 } end; { FindNearest 測試 } procedure TForm1.Button6Click(Sender: TObject); begin ClientDataSet1.IndexFieldNames := 'Name'; ClientDataSet1.FindNearest(['趙']); ShowMessage(ClientDataSet1.FieldValues['Age']); { 55 } end;
索引與排序
索引的目的有三: 快速定位、排序、建立主從表.
相關屬性與方法:

IndexDefs; { } IndexFieldCount; { } IndexFieldNames; { } IndexFields[]; { } IndexName; { } AddIndex(); { } DeleteIndex(); { } GetIndexInfo(); { } GetIndexNames(); { }
添加索引的方法有二:
- 用 IndexFieldNames 通過字段名(多個字段用 ; 隔開)指定臨時索引;
- 通過 IndexDefs.AddIndexDef 或 AddIndex 建立索引, 然后用 IndexName 指定為當前索引.
兩種方法都可以在設計時完成; 后者會有更多功能, 譬如倒排序;
兩種方法是互斥的, 指定一個會自動取消另一個.
TClientDataSet 會自動生成兩個默認索引:
- DEFAULT_ORDER、CHANGEINDEX; 它們都不允許用戶刪改.
- CHANGEINDEX 是用於 Delta(日志)的.
- DEFAULT_ORDER 可用於恢復默認排序;
- 它可能已經和某些字段關聯, 如(xml 源碼):<PARAMS DEFAULT_ORDER="1" PRIMARY_KEY="1" ... /> 或<PARAMS DEFAULT_ORDER="1 2" PRIMARY_KEY="1 2" ... />
關於臨時索引最常用的代碼是在 DBGrid 的 OnTitleClick 事件中更換索引, 如:

{ 根據當前字段排序 } procedure TForm1.DBGrid1TitleClick(Column: TColumn); begin if not Column.Field.IsBlob then { 不能給大二進制字段建立索引或排序 } ClientDataSet1.IndexFieldNames := Column.FieldName; end; { 恢復默認排序 } procedure TForm1.Button1Click(Sender: TObject); begin ClientDataSet1.IndexName := 'DEFAULT_ORDER'; end;
使用 IndexFieldNames 可指定多個字段, 如: ClientDataSet1.IndexFieldNames := '字段x; 字段y; 字段z';
此時順序很重要, 這里會先按 "字段x" 排序; 在 "字段x" 的值相同時會按 "字段y" 排序; 在 "字段y" 的相同時...
IndexFieldNames 沒有更多了, 更復雜的排序就需要建立排序對象(TIndexDef)了.
實現倒排序的例子:

{ 下面是在 holdings.xml 的基礎上建立的兩個索引; ACCT_NBR、SYMBOL 是其中的兩個字段 } procedure TForm1.FormCreate(Sender: TObject); begin ClientDataSet1.AddIndex('Index_1', 'ACCT_NBR; SYMBOL', []); { 正序 } ClientDataSet1.AddIndex('Index_2', 'ACCT_NBR; SYMBOL', [ixDescending]); { 倒序 } ClientDataSet1.IndexName := 'Index_1'; end; { 切換上面建立的兩個索引 } procedure TForm1.Button1Click(Sender: TObject); begin if ClientDataSet1.IndexName = 'Index_1' then ClientDataSet1.IndexName := 'Index_2' else ClientDataSet1.IndexName := 'Index_1'; ClientDataSet1.First; end; { 上面的 TForm1.FormCreate 過程也可以寫作(另一種建立方法) } procedure TForm1.FormCreate(Sender: TObject); begin with ClientDataSet1.IndexDefs.AddIndexDef do begin Name := 'Index_1'; Fields := 'ACCT_NBR; SYMBOL'; end; with ClientDataSet1.IndexDefs.AddIndexDef do begin Name := 'Index_2'; Fields := 'ACCT_NBR; SYMBOL'; Options := [ixDescending]; end; ClientDataSet1.IndexName := 'Index_1'; end;
關於 AddIndex

AddIndex ( const Name: string; { 索引名稱; 不能重名 } const Fields: string; { 索引字段; 多個字段用分號隔開; 默認升序排列 } Options: TIndexOptions; { 選項 } const DescFields, { 按降序排列的字段; 須先在 Fields 中列出 } const CaseInsFields: string; { 不區分大小寫的字段; 須先在 Fields 中列出 } const GroupingLevel: Integer { 分組級別, 用於分組統計的 } ); //Options: IxPrimary { 主索引 } IxUnique { 字段值無重復 } ixDescending { 降序 } ixCaseInsensitive { 不區分大小寫 } ixExpression { 無用 } ixNonMaintained { 無用 } { 可選空值 [], 最多不能多於兩個選項 } { 若是兩個選項, 其中之一須是: ixDescending 或 ixCaseInsensitive }
AddIndex 的一些用法(都是先 F1 后 F2)

//F1、F2 降序, 兩種寫法一樣: AddIndex('Index_1', 'F1; F2', [ixDescending]); AddIndex('Index_2', 'F1; F2', [], 'F1; F2'); //F1、F2 不區分大小寫排序(不指定降序則默認升序): AddIndex('Index_1', 'F1; F2', [ixCaseInsensitive]); AddIndex('Index_2', 'F1; F2', [], '', 'F1; F2'); //F1 升序, F2 降序: AddIndex('Index_1', 'F1; F2', [], 'F2'); AddIndex('Index_2', 'F1; F2', [ixDescending], 'F2'); { 此時 [ixDescending] 被忽略 } //F1 降序, F2 升序: AddIndex('Index_1', 'F1; F2', [], 'F1'); AddIndex('Index_2', 'F1; F2', [ixDescending], 'F1');
AddIndex 能做到的, 用 IndexDefs.AddIndexDef 也可以, 並且也都能在設計時完成
分組統計
運行時實現的分組統計:

//前期只需要添加 ClientDataSet1、DataSource1、DBGrid1; 事件只需要關聯窗體的 OnCreate unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, Grids, DBGrids, DB, DBClient; type TForm1 = class(TForm) DBGrid1: TDBGrid; DataSource1: TDataSource; ClientDataSet1: TClientDataSet; procedure FormCreate(Sender: TObject); private procedure OnGetText_Agg1(Sender: TField; var Text: string; DisplayText: Boolean); procedure OnGetText_Agg2(Sender: TField; var Text: string; DisplayText: Boolean); public end; var Form1: TForm1; implementation {$R *.dfm} procedure TForm1.FormCreate(Sender: TObject); begin { 關聯數據控件 } DBGrid1.DataSource := DataSource1; DataSource1.DataSet := ClientDataSet1; { 先打開前面例子中留下的測試文件 } ClientDataSet1.LoadFromFile('C:\Temp\Test.xml'); { 添加索引, 其中分組級別是 2 } ClientDataSet1.AddIndex('Index1', '班級;年齡', [], '', '', 2); { 給數據集指定此索引 } ClientDataSet1.IndexName := 'Index1'; { 數據結構變化時一般需要先關閉數據集 } ClientDataSet1.Close; { 添加統計字段 Agg1: 按班分組統計語文總成績 } with TAggregateField.Create(Self) do begin FieldName := 'Agg1'; Expression := 'Sum(語文成績)'; IndexName := 'Index1'; GroupingLevel := 1; Active := True; OnGetText := OnGetText_Agg1; DataSet := ClientDataSet1; end; { 添加統計字段 Agg2: 各班分別按年齡分組統計語文總成績 } with TAggregateField.Create(Self) do begin FieldName := 'Agg2'; Expression := 'Sum(語文成績)'; IndexName := 'Index1'; GroupingLevel := 2; Active := True; OnGetText := OnGetText_Agg2; DataSet := ClientDataSet1; end; { 需要在 DBGrid 中顯示的字段 } with DBGrid1.Columns do begin Add.FieldName := '班級'; Add.FieldName := '姓名'; Add.FieldName := '年齡'; Add.FieldName := '語文成績'; Add.FieldName := 'Agg1'; Add.FieldName := 'Agg2'; end; { 打開數據集並激活統計 } ClientDataSet1.Open; ClientDataSet1.AggregatesActive := True; end; procedure TForm1.OnGetText_Agg1(Sender: TField; var Text: string; DisplayText: Boolean); begin if gbLast in ClientDataSet1.GetGroupState(1) then Text := Sender.AsString else Text := ''; end; procedure TForm1.OnGetText_Agg2(Sender: TField; var Text: string; DisplayText: Boolean); begin if gbLast in ClientDataSet1.GetGroupState(2) then Text := Sender.AsString else Text := ''; end; end.
參考文章
http://www.cnblogs.com/Dragon7/archive/2011/08/31/2161244.html
http://www.cnblogs.com/del/