4.1.3 Filter(过滤器)
除了Locate和Lookup之外,事实上,Delphi的过滤器功能是非常有用的,因为过滤器不但能够查寻数据,更棒的是它可以再把结果数据集中的数据分门别类,让应用程序只存取特定群组的数据。这个行为有点像是把从后端数据库回传的结果数据集中的数据,再使用额外的条件来取得子结果数据集。
Delphi的过滤器功能不但可以让开发人员对单一字段下达过滤条件,也可以对多个字段下达过滤条件,同时不限定字段是否为索引字段。此外开发人员有两种方法来使用过滤器,第一种方法是使用TSimpleDataSet/TClientDataSet控件的Filter特性值,第二种方法是使用TSimpleDataSet/TClientDataSet控件的OnFilterRecord事件处理函数。
这两种方法的不同点是,Filter特性值只能让开发人员使用字符串来设定过滤条件,而OnFilterRecord事件处理函数却能够让开发人员使用Delphi程序语言来执行任何复杂的过滤条件。Delphi提供和过滤功能相关的特性和事件处理函数如表4-1所示。
表4-1
|
特性/事件处理函数 |
意义 |
|
Filter特性值 |
使用字符串条件来过滤数据 |
|
OnFilterRecord事件处理函数 |
使用事件处理函数程序代码来过滤数据 |
|
Filtered |
决定是否打开过滤器功能的特性值 |
|
FilterOptions |
过滤功能使用的额外条件 |
要使用过滤器功能,开发人员必须设定Filtered特性值为True。一旦Filtered特性值为True之后,如果Filter特性值有任何的过滤条件,Delphi便会以这个过滤条件作为标准来过滤目前在结果数据集之中的数据。只有符合过滤条件的数据才能够显示在数据感知控件之中,或是被存取,而不符合过滤条件的数据暂时无法被存取到。此外如果开发人员有定义OnFilterRecord事件处理函数,那么Delphi也会执行OnFilterRecord事件处理函数来过滤数据。因此,如果开发人员同时设定了Filter特性值以及OnFilterRecord事件处理函数,那么这两个过滤条件都会被执行。
至于FilterOptions特性则类似Locate的第三个参数,用来指明在过滤数据时是否需要分别大小写,或是是否需要比对完整的字符串过滤值。
现在就让我们看看如何使用过滤器来过滤数据,首先修改图4-5的主窗体,在主窗体中加入另外三个按钮,Filter、OnFilterRecord和SetRange,如图4-12所示,以便在范例应用程序使用过滤器和SetRange方法时查寻数据。

图4-12 在主窗体中加入Filter、OnFilterRecord和SetRange按钮
现在我们要在范例应用程序中使用过滤器功能来查寻数据,请双击主窗体中的【Filter】按钮,并且在它的事件处理函数中撰写如下的程序代码:
procedure TfrmMain.btnFilterClick(Sender: TObject);
begin
dmSearchData.sqlcdsTest.Filtered := False;
dmSearchData.sqlcdsTest.Filter := edtResult.Text;
dmSearchData.sqlcdsTest.Filtered := True;
end;
上面的程序代码首先关闭过滤器功能,因为现在我们要改变过滤条件,接着把edtReturn控件中用户输入的过滤条件设定给TSimpleDataSet的Filter特性值,最后再设定TSimpleDataSet的Filtered特性值以便打开过滤器功能,并且使用新的过滤条件来查寻(过滤)数据。
图4-13是执行范例应用程序,并且在edtReturn控件中输入下面的过滤条件:
LENGTH_CM>=60 AND LENGTH_CM <= 80
接着点击主窗体中的【Filter】按钮,那么,我们就可以在数据感知控件中看到只有符合这个过滤条件的数据会显示出来。
请注意现在我们的过滤条件是以LENGTH_CM这个字段为目标,而LENGTH_CM字段并不是索引字段,但是dbExpress的过滤器功能仍然能够找到查寻数据。

图4-13 执行范例应用程序并且使用过滤器来查寻数据
当然,过滤器并不是只能以一个字段为过滤目标,开发人员可以同时使用数个字段来过滤数据,例如下面的过滤条件同时以LENGTH_CM和TOPOTYPE这两个字段为目标来查寻数据。请注意由于TOPOTYPE字段是字符串类型,因此它的查寻数值必须使用单引号来设定。
LENGTH_CM>=10 AND LENGTH_CM <= 80 AND TOPOTYPE='China'
图4-14便是执行这个过滤情形后的画面,我们看到范例应用程序仍然找到了符合过滤条件的数据。
dbExpress的过滤器功能非常地强劲,它几乎没有什么限制。不过如果你觉得使用字符串值来设定过滤条件仍然不够,那么你还可以使用OnFilterRecord事件处理函数来处理过滤条件。

图4-14 执行范例应用程序并且使用过滤器来查寻数据
使用OnFilterRecord事件处理函数
TClientDataSet/TSimpleDataSet控件有一个OnFilterRecord事件处理函数可以让开发人员使用Object Pascal程序代码来集合任何复杂的过滤条件。OnFilterRecord事件处理函数的原型如下:
procedure FilterRecord(DataSet: TDataSet; var Accept: Boolean);
当开发人员打开过滤器功能时,Delphi会为每一笔在结果数据集中的数据调用一次OnFilterRecord事件处理函数,其中OnFilterRecord事件处理函数的第一个参数便是目前执行过滤功能的数据集控件,而第二个参数Accept是一个var类型的布尔值参数。如果在OnFilterRecord事件处理函数中这笔数据符合过滤条件的话,那么开发人员就必须设定Accept为True,以便让这笔数据存在于经过过滤之后的子结果数据集之中。相反,如果这笔数据不符合过滤条件,那么开发人员就必须设定Accept参数为False以剔除这笔数据。
现在就让我们在范例应用程序中使用OnFilterRecord事件处理函数来过滤数据。在刚才使用过滤器的范例中,我们以LENGTH_CM和TOPOTYPE这两个字段为目标来查寻数据。如果现在我们希望寻找LENGTH_CM在10和50之间,并且TOPOTYPE是以'China'为开头的数据,那么使用OnFilterRecord事件处理函数可以非常简单地找到我们需要的数据。
现在请双击图4-12中的OnFilterRecord事件处理函数,并且在它的OnClick事件处理函数中撰写如下的程序代码:
procedure TfrmMain.btnOnFilterRecordClick(Sender: TObject);
begin
dmSearchData.sqlcdsTest.Filtered := False;
dmSearchData.sqlcdsTest.Filter := '';
dmSearchData.sqlcdsTest.Filtered := True;
end;
在上面的程序代码中我们先关闭过滤器功能,并且清除TSimpleDataSet的Filter特性值以避免dbExpress同时以Filter特性值和OnFilterRecord事件处理函数来过滤数据。
接着在sqlcdsTest的OnFilterRecord事件处理函数中撰写如下的程序代码:
procedure TdmSearchData.sqlcdsTestFilterRecord(DataSet: TDataSet;
var Accept: Boolean);
begin
if ( (DataSet.FieldByName('LENGTH_CM').Value >= 10) and
(DataSet.FieldByName('LENGTH_CM').Value <= 50) and
( Pos('China', DataSet.FieldByName('TOPOTYPE').Value ) <> 0 ) ) then
Accept := True
else
Accept := False;
end;
在上面的程序代码中先使用第一个参数DataSet来找到目前正在被过滤的数据,判断它的'LENGTH_CM'字段值是否在10和50之间,而且'TOPOTYPE'字段是不是以'China'为开头的数值,如果是的话就设定Accept为True,反之则设定Accept为False。
现在执行范例应用程序并且点击主窗体中的OnFilterRecord按钮,就可以看到如图4-15所示的画面。

图4-15 执行范例应用程序并且使用OnFilterRecord事件处理函数来查寻数据
范例应用程序果然顺利地找到了符合过滤条件的数据。请注意,由于过滤器功能对每一笔在结果数据集中的数据都会进行一次过滤的操作,因此,如果结果数据集中的数据数量很大,那么在使用过滤器功能之前,开发人员必须很小心地评估过滤器的影响。
使用过滤器的场合
虽然过滤器可以像TClientDataSet/TSimpleDataSet的Locate和Lookup一样来查寻数据,但是过滤器另外有一个非常特别的用途,那就是它可以在结果数据集中再以过滤条件来取得子结果数据集。这个用途在一些应用中是非常方便的,让我们使用一个例子和图4-16来说明这个用途。
假设在你的应用程序中已经使用SQL命令,从后端数据中抽取了数量不多的数据到结果数据集中,例如要使用SQL命令查寻在台北市使用Delphi而且购买了本书的开发人员。又想根据这些读者以行政区为分界来分析购买的情形,比如在大安区、文山区和中山区等区域,那么我们仍然可以SQL命令来存取数据,但是既然这些数据已经存在于结果数据集中,我们就可以直接使用过滤器的功能来取得数据,而不需要再使用许多的SQL命令来重新从后端数据中抽取,这种处理数据的方式的执行效率会比重新使用SQL命令更有效(见图4-16)。

图4-16 使用过滤器在结果数据集中过滤子结果数据集
在笔者平常撰写数据库应用程序时,也经常地使用这个技巧从结果数据集中存取笔者需要的数据。由于过滤器的执行效率在结果数据集中数据不多时是不错的,因此开发人员也可以善用这个技巧来处理数据。当然笔者希望刚才我举的例子不会真正发生,否则这本书可能就看不到再版了,哈哈。






