3.1.3 在TSimpleDataSet中动态排序
在前面讨论的排序技巧中,大都是属于在应用程序设计时期设定的排序条件,虽然很方便,但是毕竟弹性不够大。在许多的数据应用中,应用程序需要对数据进行动态的排序方式。例如在应用程序设计时期,程序员在SortFieldNames特性中使用了SPECIES_NAME字段作为排序条件,但是在应用程序执行时,用户可能会想要使用其他的字段来查看数据。这种在应用程序执行时期动态改变数据排序的需求是经常看到的。那么,dbExpress提供了什么方法可以帮助程序员在应用程序执行时期对数据进行动态排序呢?
dbExpress基本上提供了两种方法让程序员可以在应用程序执行时期动态排序数据。第一种是使用AddIndex,第二种是使用IndexDefs。这两种不同的动态排序方式各有特点,在下面的章节中将会讨论如何使用这两种动态排序技巧。
使用 AddIndex
TCustomClientDataSet类型的AddIndex方法可以为已经存在的数据表动态建立新的索引信息。在AddIndex方法中,程序员可以指定要作为索引的字段以及如何排序这些索引字段的数据,下面是AddIndex的函数原型:
procedure AddIndex(const Name, Fields:
string; Options: TIndexOptions; const
DescFields: string = ''; const CaseInsFields: string = ''; const
GroupingLevel:
Integer = 0 );
AddIndex接受数个参数,而这些参数和第三个参数Options有着相当的关系。第三个参数Options允许程序员指定使用任何规则来进行排序的工作,例如程序员可以指定数据是以升序、降序或是不需要管大小写排列,或是字段之中所有的数据都必须是唯一的值。
第三个参数Options可以指定如下的数值:
type
TIndexOption =
(ixPrimary, ixUnique, ixDescending, ixCaseInsensitive,
ixExpression, ixNonMaintained);
TIndexOptions = set of TIndexOption;
表3-1整理了TIndexOption数值代表的意义:
表3-1
|
数值 |
意义 |
|
IxPrimary |
此索引是此数据集的主索引对象 |
|
IxUnique |
此索引表示字段中的数据都必须是唯一的,不可以有重复的字段数值 |
|
ixDescending |
以降序方式排序数据 |
|
ixCaseInsensitive |
不管大小写的差异来排序数据 |
|
ixExpression |
这个索引是使用dBase的表达式来定义的(此数值只适合使用在dBase的数据库中) |
|
ixNonMaintained |
这种索引在数据更改时,不会同时自动更新索引对象的信息 |
如果程序员指定了第二个参数Fields的数值,那么数据排序的方式就以第三个参数Options制定的方式进行排序。
因此,如果第三个参数Options是ixDescending,那么程序员在调用AddIndex时就必须指定第四个参数DescFields的数值。DescFields是所有需要作为索引的字段名称,每一个字段以分号“;”分隔。如果Options是ixCaseInsensitive,那么调用AddIndex时就必须指定第四个参数CaseInsFields的数值,每一个字段也使用分号“;”分隔。或当Options是其他的数值时,那么就必须指定第二个参数Fields的数值。至于AddIndex的第一个参数则是此新建索引的名称。
了解了如何使用AddIndex动态建立索引以排序数据后,接着就让我们继续在前面的范例应用程序中加入使用AddIndex排序数据的能力。
首先双击图3-6中【排序-AddIndex】按钮,并且在它的OnClick事件处理函数中撰写如下的程序代码:
procedure TForm1.btnAddIndexClick(Sender: TObject);
var
sIndexName : String;
begin
if (AlreadIsIndex(INDEXID + cbFields.Text)) then
DeleteIndex(cbFields.Text);
sIndexName := AddIndexForSCDS(cbFields.Text, rbAscending.Checked);
dmSortDemo.scdsDemo.IndexName := sIndexName;
Delete(sIndexName, 1, Length(INDEXID));
slIndex.Add(INDEXID + sIndexName);
dmSortDemo.scdsDemo.First;
end;
在上面的程序代码中,首先程序代码会调用AlreadIsIndex方法来检查用户在主窗体edtSortFields控件中输入要建立的是否已经是索引字段了。如果是,就先调用DeleteIndex删除已经建立的索引对象,再准备重新根据用户输入的字段以及在主窗体中选择使用升序或是降序的方式建立索引。
接着btnAddIndexClick函数调用AddIndexForSCDS方法,根据edtSortFields控件中的字段名称以及用户选择的排序方式来建立索引对象。在AddIndexForSCDS方法执行完毕之后,新的索引对象便已经建立了,最后设定数据模块中的TSimpleDataSet控件的IndexName为新建立的索引名称,以便使用它来排序数据。
AlreadIsIndex方法会在slIndex中查寻是否已经存在了参数sIndex指明的索引名称,并且回传查寻的结果。
function TForm1.AlreadIsIndex(const sIndex: String): Boolean;
var
iCount : Integer;
begin
Result := False;
for iCount := 0 to slIndex.Count - 1 do
begin
if (sIndex = slIndex.Strings[iCount]) then
begin
Result := True;
Break;
end;
end;
end;
由于slIndex中储存的是索引的名称信息,以避免用户在TSimpleDataSet中建立重复的索引信息,因此,在范例程序开始执行之前,我们先调用TSimpleDataSet的GetIndexNames方法,以便在slIndex中预先储存TSimpleDataSet所有的信息。同时调用FillFields在窗体中的TComBox控件填入TSimpleDataSet的字段名称信息。
procedure TForm1.FormShow (Sender: TObject);
begin
if (slIndex.Count = 0) then
dmSortDemo.scdsDemo.GetIndexNames(slIndex);
if (cbFields.Items.Count = 0) then
FillFields(cbFields);
edtSortFields.Text := dmSortDemo.sdsDemo.SortFieldNames;
end;
至于为TSimpleDataSet真正建立索引信息的方法则是AddIndexForSCDS,在AlreadIsIndex方法成功地检查目前要建立的索引尚不存在之后,范例程序才调用AddIndexForSCDS方法建立索引信息。AddIndexForSCDS方法会判断用户在窗体中选择的升/降序方式,并且调用AddIndex实际为TSimpleDataSet建立索引信息。
function TForm1.AddIndexForSCDS(const sIndex: String;
bAcsending: Boolean) : String;
begin
Result := INDEXID + sIndex;
if (bAcsending) then
dmSortdemo.scdsDemo.AddIndex(Result, sIndex, [])
else
dmSortdemo.scdsDemo.AddIndex(Result, sIndex, [ixDescending]);
end;
最后,范例程序也提供了删除索引信息的功能,要删除TSimpleDataSet中的索引信息,只需要调用DeleteIndex方法即可。DeleteIndex接受的参数就是欲删除的索引名称。因此,在范例程序的DeleteIndex方法中就直接调用TSimpleDataSet的DeleteIndex方法。
procedure TForm1.DeleteIndex(const sIndex: String);
begin
dmSortDemo.scdsDemo.DeleteIndex(INDEXID + sIndex);
end;
现在编译此范例程序并执行它,让我们为数个字段动态建立索引并且观察执行的结果。图3-8显示的就是在范例程序中为SPECIES_NAME和LENGTH_CM字段建立索引的结果。请注意在画面中证明我们不但可以正确地通过AddIndex建立索引,同时AddIndex也可以正确地建立升序或是降序索引。

图3-8 使用AddIndex动态为SPECIES_NAME和LENGTH_CM字段建立升序、降序索引
到目前为止,我们已经讨论了数种不同的方法为TSimpleDataSet/TClientDataSet建立索引以排序数据,接下来让我们讨论另外一种排序数据的技巧。
使用IndexDefs和IndexName
除了AddIndex之外,程序员还可以通过TSimpleDataSet/TClientDataSet的IndexDefs和IndexName特性值同时以多个字段来进行数据的排序。使用IndexDefs和IndexName进行排序非常地简单,首先程序员调用IndexDefs的AddIndexDef方法建立一个TIndexDef对象,然后在TIndexDef对象中设定各种索引的信息,例如索引名称,索引升序、降序格式,索引的目标字段等,和前面的AddIndex使用方式非常地相像,最后再把TIndexDef对象的索引名称指定给TSimpleDataSet的IndexName特性值,那么TSimpleDataSet就会使用TIndexDef对象指定的索引方式来排序其中的数据。
图3-6中的【排序-IndexDefinition】按钮的事件处理函数就使用了TSimpleDataSet的IndexDefs和IndexName特性值来进行动态排序数据的工作。在下面的【排序-IndexDefinition】按钮的事件处理函数中,先调用AlreadIsIndexDef方法检查由窗体中edtSortFields这个TEdit控件指定的字段是否已经建立了索引信息,如果没有,就调用CreateIndex方法来建立动态索引,最后把CreateIndex建立的索引名称指定给数据模块中TSimpleDataSet的IndexName值,以实际排序TSimpleDataSet中的数据。
procedure TForm1.btnIndexDefsClick(Sender: TObject);
var
sIndexName : String;
begin
if (not AlreadIsIndexDef(edtSortFields.Text)) then
begin
sIndexName := CreateIndex(edtSortFields.Text, rbAscending.Checked);
dmSortDemo.scdsDemo.IndexName := sIndexName;
dmSortDemo.scdsDemo.First;
end;
end;
AlreadIsIndexDef方法则是使用IndexDefs的FindIndexForFields方法在TSimpleDataSet中查寻用户指定的字段是否已经建立过索引了。如果用户已经使用过相同的字段建立过索引,那么FindIndexForFields会回传代表这些字段的TIndexDef对象。在FindIndexForFields方法检查之后,我们还需要调用IndexDefs的Find方法进行相同的检查,只是这次检查是在字段名称之前加上INDEXID这个代表索引前置名称的字符串。
function TForm1.AlreadIsIndexDef(const sIndex: String): Boolean;
var
adxf : TIndexDef;
begin
Result := False;
adxf := nil;
try
adxf := dmSortDemo.scdsDemo.IndexDefs.FindIndexForFields(sIndex);
if (Assigned(adxf)) then
begin
Result := True;
exit;
end;
except
on exception do;
end;
try
adxf := dmSortDemo.scdsDemo.IndexDefs.Find(INDEXID + sIndex);
if (Assigned(adxf)) then
Result := True;
except
on Exception do;
end;
end;
如果AlreadIsIndexDef没有查寻到用户指定字段建立的索引信息,那么CreateIndex便会被执行以实际建立的索引。CreateIndex调用IndexDefs的AddIndexDef方法建立TIndexDef对象,再指定TIndexDef对象的Name特性值以设定索引名称,设定TIndexDef对象的Fields特性值以指定排序的字段名称,每一个字段名称也是使用分号“;”分隔的。TIndexDef对象的Options可以设定排序的特性,例如升序、降序或是不分大小写方式排序。最后CreateIndex回传建立的索引名称,以便指定给TSimpleDataSet的IndexName特性值。
function TForm1.CreateIndex(const sIndex:
String;
bAcsending : Boolean) : String;
var
adxf : TIndexDef;
begin
adxf := dmSortDemo.scdsDemo.IndexDefs.AddIndexDef;
adxf.Name := INDEXID + sIndex;
adxf.Fields := sIndex;
if (not bAcsending) then
adxf.Options := [ixDescending];
Result := adxf.Name;
end;
现在如果我们再次执行范例程序,如图3-9所示,就可以看到IndexDefs和IndexName对于数据进行排序的效果了。


图3-9 使用IndexDefs进行多字段动态排序
前面讨论了许多排序数据的技巧,有的非常简单,只需要使用对象查看器设定即可,有的比较有弹性,但是需要使用程序代码来进行动态排序。不管使用哪一种方式似乎都可以达到排序数据的目的,非常方便。不过,这是不是代表程序员可以自由地选择自己喜欢的技巧来排序数据呢?排序数据有没有什么限制呢?






