【delphi】ClientDataSet详细解读
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 := ;
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 := ; { 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([, '张三', , True]);
ClientDataSet1.AppendRecord([, '李四', , False]);
ClientDataSet1.AppendRecord([, '王五', , 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, );
Add('Age', ftWord);
Add('Sex', ftBoolean);
end; { 构建数据集, 不可缺少的一步 }
ClientDataSet1.CreateDataSet; { 显示; 如果在设计时已挂接或不需要显示, 可省略下两行 }
DataSource1.DataSet := ClientDataSet1;
DBGrid1.DataSource := DataSource1; { 插入数据 }
ClientDataSet1.InsertRecord([, '张三', , True]);
ClientDataSet1.InsertRecord([, '李四', , False]);
ClientDataSet1.InsertRecord([, '王五', , 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[].FieldName; { 获取第一个字段值的几种方法: }
v1 := ClientDataSet1.Fields[].Value;
v2 := ClientDataSet1.FieldByName(fName).Value;
v3 := ClientDataSet1.FindField(fName).Value;
v4 := ClientDataSet1.FieldValues[fName];
v5 := ClientDataSet1[fName]; { FieldValues 是默认的数组属性 }
v6 := ClientDataSet1.FieldList[].Value;
v7 := ClientDataSet1.FieldList.FieldByName(fName).Value;
v8 := ClientDataSet1.FieldList.Find(fName).Value;
v9 := ClientDataSet1.FieldList.Fields[].Value; { 已知这个字段是 Double 类型的, 可同时转换 }
num := ClientDataSet1.Fields[].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 := to ClientDataSet1.FieldCount - do
begin
Memo1.Lines.Add(ClientDataSet1.Fields[i].Value);
end;
Memo1.Lines.Add(''); for i := to ClientDataSet1.FieldList.Count - 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[].AsString; { 读取倒数第二行第二个字段 }
ClientDataSet1.Last;
ClientDataSet1.Prior;
s2 := ClientDataSet1.Fields[].AsString; { 读取第四行第二个字段 }
ClientDataSet1.RecNo := ;
s3 := ClientDataSet1.Fields[].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[].Value);
ClientDataSet1.Next;
end;
Memo1.Lines.Add('-------'); for i := to ClientDataSet1.RecordCount do
begin
ClientDataSet1.RecNo := i;
Memo1.Lines.Add(ClientDataSet1.FieldList[].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 := to List.Count - 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[..] of AnsiChar;
begin
ClientDataSet1.GetFieldData(ClientDataSet1.Fields[], @F1);
ClientDataSet1.GetFieldData(ClientDataSet1.Fields[], @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 := ;
TimeStamp.Date := Data.Date;
end;
ftTime:
begin
TimeStamp.Time := Data.Time;
TimeStamp.Date := DateDelta;
end;
else
try
TimeStamp := MSecsToTimeStamp(Data.DateTime);
except
TimeStamp.Time := ;
TimeStamp.Date := ;
end;
end;
Result := TimeStampToDateTime(TimeStamp);
end; { GetCurrentRecord 是把当前行的所有字段(不包括 Blob 字段)读入到缓冲区 }
procedure TForm1.Button7Click(Sender: TObject);
type
THoldingsStruct = packed record { 这是根据 holdings.xml 建立的数据结构 }
ACCT_NBR: Double;
SYMBOL: array[..] of AnsiChar; { 其 Size=7, 但后面还有个 #0 }
SHARES: Double;
PUR_PRICE: Double;
PUR_DATE: Integer;
// Other: array[..] 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, );
FieldDefs.Add('Age', ftWord);
CreateDataSet;
AppendRecord([, '赵AB', ]);
AppendRecord([, '钱AB', ]);
AppendRecord([, '孙AB', ]);
AppendRecord([, '李AB', ]);
AppendRecord([, '赵ab', ]);
AppendRecord([, '钱ab', ]);
AppendRecord([, '孙ab', ]);
AppendRecord([, '李ab', ]);
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',]), []) 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',]), 'Age');
if not VarIsNull(R) then ShowMessage(R); { 66 } R := ClientDataSet1.Lookup('ID', , 'Name;Age');
if VarIsArray(R) then
for i := VarArrayLowBound(R, ) to VarArrayHighBound(R, ) 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', ]) 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', '班级;年龄', [], '', '', );
{ 给数据集指定此索引 }
ClientDataSet1.IndexName := 'Index1'; { 数据结构变化时一般需要先关闭数据集 }
ClientDataSet1.Close; { 添加统计字段 Agg1: 按班分组统计语文总成绩 }
with TAggregateField.Create(Self) do begin
FieldName := 'Agg1';
Expression := 'Sum(语文成绩)';
IndexName := 'Index1';
GroupingLevel := ;
Active := True;
OnGetText := OnGetText_Agg1;
DataSet := ClientDataSet1;
end;
{ 添加统计字段 Agg2: 各班分别按年龄分组统计语文总成绩 }
with TAggregateField.Create(Self) do begin
FieldName := 'Agg2';
Expression := 'Sum(语文成绩)';
IndexName := 'Index1';
GroupingLevel := ;
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() then
Text := Sender.AsString else Text := '';
end; procedure TForm1.OnGetText_Agg2(Sender: TField; var Text: string; DisplayText: Boolean);
begin
if gbLast in ClientDataSet1.GetGroupState() then
Text := Sender.AsString else Text := '';
end; end.
参考文章
http://www.cnblogs.com/Dragon7/archive/2011/08/31/2161244.html
http://www.cnblogs.com/del/
【delphi】ClientDataSet详细解读的更多相关文章
- 【转】【delphi】ClientDataSet详细解读
原文:http://www.cnblogs.com/lcw/p/3496764.html TClientDataSet的基本属性和方法 TClientDataSet控件继承自TDataSet,其数据存 ...
- MemCache超详细解读
MemCache是什么 MemCache是一个自由.源码开放.高性能.分布式的分布式内存对象缓存系统,用于动态Web应用以减轻数据库的负载.它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高 ...
- MemCache超详细解读 图
http://www.cnblogs.com/xrq730/p/4948707.html MemCache是什么 MemCache是一个自由.源码开放.高性能.分布式的分布式内存对象缓存系统,用于 ...
- Delphi TStream 详细介绍
Delphi TStream 详细介绍Stream对象,又称流式对象,是TStream.THandleStream.TFileStream.TMemoryStream.TResourceStream和 ...
- rpm软件包管理的详细解读
CentOS系统上使用rpm命令管理程序包:安装.卸载.升级.查询.校验.数据库维护 1.基本安装 rpm -ivh PackageFile 2.rpm选项 rpm -ivh --test Packa ...
- MemCache详细解读
MemCache是什么 MemCache是一个自由.源码开放.高性能.分布式的分布式内存对象缓存系统,用于动态Web应用以减轻数据库的负载.它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高 ...
- Android BLE蓝牙详细解读
代码地址如下:http://www.demodashi.com/demo/15062.html 随着物联网时代的到来,越来越多的智能硬件设备开始流行起来,比如智能手环.心率检测仪.以及各式各样的智能家 ...
- 为你详细解读HTTP请求头的具体含意
当我们打开一个网页时,浏览器要向网站服务器发送一个HTTP请求头,然后网站服务器根据HTTP请求头的内容生成当次请求的内容发送给浏览器.你明白HTTP请求头的具体含意吗?下面一条条的为你详细解读,先看 ...
- 详细解读Volley(三)—— ImageLoader & NetworkImageView
ImageLoader是一个加载网络图片的封装类,其内部还是由ImageRequest来实现的.但因为源码中没有提供磁盘缓存的设置,所以咱们还需要去源码中进行修改,让我们可以更加自如的设定是否进行磁盘 ...
随机推荐
- 用dockerfile构建基于centos系统的jar包的镜像
实际示例: [root@master01 home-dataline]# ls dataline.jar Dockerfile jdk-8u181-linux-x64.tar.gz [root@mas ...
- ios中webview的高级用法(二)
框架: webview与js的通信框架 #import "MJViewController.h" #import "MBProgressHUD+Add.h" ...
- js触摸事件
touch事件的绑定 电脑端的mouseDown,mouseUp,mouseMove分别对应移动端的touchstart,touchend,touchmove 下面的代码判断浏览器是电脑端还是移动端, ...
- STDIN_FILENO的作用及与stdin 的区别
1.STDIN_FILENO的作用 STDIN_FILENO属于系统API接口库,其声明为 int 型,是一个打开文件句柄,对应的函数主要包括 open/read/write/close 等系统级调用 ...
- mac 终端添加颜色
1.打开终端,然后找到终端偏好设置,选择自己喜欢的颜色 2.然后切换到当前用户的家目录: cd ~ 3.打开文件,开始编辑".bash_profile", 添加下面两句 expor ...
- Android Studio 常见问题汇总
一.字体大小问题 在android studio的使用过程中没有发现类似于Eclipse中的font选项,调节字体大小方法如下: 1.File---- >Settings,找到Editor 2. ...
- [bug]WCF 内存入口检查失败 Memory gates checking failed
bug描述 异常信息:内存入口检查失败,因为可用内存(xxx 字节)少于总内存的 xx%.因此,该服务不可用于传入的请求.若要解决此问题,请减少计算机上的负载,或调整 serviceHostingEn ...
- 微信支付服务器CA证书更换服务器安装der证书的方法 DigiCert的根证书
[重要]微信支付服务器证书更换通知,请开发人员验证以免影响交易 尊敬的微信支付商户&服务商: 因微信支付HTTPS服务器证书的根CA证书将于2018年8月23日到期失效,微信支付计划于2018 ...
- list、map、数组 转换
list,set,map,数组间的相互转换1.list转setSet set = new HashSet(new ArrayList()); 2.set转listList list = new Arr ...
- [Warning] TIMESTAMP with implicit DEFAULT value is deprecated
启动mysql时,报如下警告信息: [Warning] TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explic ...