今天遇到一个问题,查询某部门的数据后,要对这些数据进行多关键字的过滤,当用户在关键字编辑框用空格分开多个关键字,按下回车时就要过滤,将包含所有关键字的记录过滤出来。比如一条记录有20个字段,关键字有3个,当20个字段中的某些字段必须包含这3个关键字才能符合要求,如果只包含1个或2个关键字就要被过滤。
按照经验我首先想到了ADO的Filter,在关键字编辑框的KeyDown事件里写代码,定义Filter字串,然后Filter,一切都那么熟练,但是问题来了,字串的模型是这样的:(字段1 like '%关键字1%' or 字段2 like '%关键字1%' or 字段3 like '%关键字1%') and
(字段1 like '%关键字2%' or 字段2 like '%关键字2%' or 字段3 like '%关键字2%') and
(字段1 like '%关键字3%' or 字段2 like '%关键字3%' or 字段3 like '%关键字3%'),如果这样的模型写成的过滤字串在过滤时一定会出现“参数类型不正确,或不在可以接受的范围之内,或与其他参数冲突。”的错误提示,原因不太清楚,查了网上的资料也没人说的清楚,我的结论是,括号组合不能用and连接,如果括号组合用or 连接是可以的,但又不符合我的业务要求,在做了1天的尝试后,我放弃了,改用OnFilterRecord事件。下面是我的解决方案
一、重点代码
procedure TForm1.ADOQueryListFilterRecord(DataSet: TDataSet; var Accept: Boolean);
var
i,j,iCount,iTotal:Integer;
sTmp:string;
KList:TStrings;
begin
inherited;
KList:=TStringList.Create;
KList.Text:=Trim(RzEdit3.Text); //获取关键字框里的内容
KList.Text:=StringReplace(KList.Text,' ',#13,[rfReplaceAll]); //空格换成回车,将文字换成列表
iTotal:=0;
for i := 0 to KList.Count -1 do
begin
if KList[i]<>'' then //获取有效关键字的总数
inc(iTotal);
end;
if iTotal=0 then //没有关键字就全部显示
begin
Accept:=True;
Exit;
end;
iCount:=0; //匹配计数器清零
for j := 0 to KList.Count -1 do //关键字逐一匹配
begin
if KList[j]='' then
Continue;
for i := 0 to DBGridList.VisibleColumns.Count -1 do
//数据集里的字段逐一匹配关键字,我用的是DBGridEh的列,只针对列表里的内容
begin
sTmp:=DBGridList.VisibleColumns[i].Field.AsString; //获取字段内容,全部用字串来匹配
if Pos(KList[j],sTmp)>0 then //字串匹配到关键字后,计数器加1
begin
Inc(iCount);
Break;
end;
end;
end;
Accept:=iCount=iTotal; //是否过滤的判断,这里匹配数要等于关键字的数量才显示,否则过滤掉
end;
说明一下
1、因为是关键字匹配,所以字段的值要转换成字串来比较,包换日期和数字型,如果有Image字段的最好跳过。
2、因为我是用列表显示,是对用户所见的内容进行匹配,所以匹配时用了DBGrid的列数,视情况也可以数据集的全字段匹配比如Query1.Fields之类
3、是否过滤的判断,这里计数器必须与关键字数量相等才显示,也可以转换成匹配度,比如
if iCount*100 / iTotal > 80 then
Accept:=true
else
Accept:=false;
二、何时过滤
按照习惯,输入关键字用空格 分开,回车时查找,所以在 关键字编辑框的KeyDown事件里写
procedure TForm1.RzEdit3KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
var
i,iTotal:Integer;
KList:TStrings;
begin
inherited;
if Key=VK_RETURN then //判断是回车才执行
begin
KList:=TStringList.Create;
KList.Text:=Trim(RzEdit3.Text); //获取关键字框里的内容
KList.Text:=StringReplace(KList.Text,' ',#13,[rfReplaceAll]); //空格换成回车,将文字换成列表
iTotal:=0;
for i := 0 to KList.Count -1 do
begin
if KList[i]<>'' then //获取有效关键字的总数,防止用户按了多个空格
inc(iTotal);
end;
if iTotal=0 then //没有关键字就全部显示
begin
Filtered:=False;
Exit;
end;
with ADOQueryList do
begin
try
Filtered:=False;
Filtered:=True;
finally
KList.Clear;
KList.Free;
end;
end;
end;
end;
说明:
1、2个事件开头部分有些重复,原因是触发过滤的地方可能不止一处,所以有些东西不能省,大家根据需要进行取舍。
2、有些人看到我的代码可能觉得我写的比较啰嗦,其实我觉得宁可多写几行代码也不要出任何漏洞,有时候辛辛苦苦写个软件,就因为有种情况你没想到结果一出现软件就出错了,结果别人就说了一句“什么软件,这也能出错?”,功劳没了,苦劳也没了。写软件难,写没BUG的软件难上加难,完善软件花的时间往往要比写软件多几倍的时间,所以建议大家在写的时候不要急不要浮躁,把所有可能会出现的问题都想一想,在写功能的时候一起补上,看似比别人慢,但最后你的软件是最好的。