在工作中,我们有一个名为" ClientdatasetGrid"的组件, 允许用户通过单击一个或多个列标题对网格记录进行排序。
我也为工作制作了一个组件,TEdit的后代,我称之为TDBFilterEdit。
一旦为其分配了DataSet或DBGrid,它就会为DataSet创建一个OnFilterRecord事件,并在您停止更改执行Event的文本之后。
只要数据集已经过滤并且用户对网格进行排序,就会出现问题。
网格组件首先删除当前的IndexDef,更新,添加新索引并再次更新,将IndexDefs添加到Clientdataset。
每当删除或添加索引时,我的OnFilterRecord事件都会被触发。 我通过禁用控件并从网格中删除OnFilterRecord事件来缓解这种情况,直到添加新索引为止。
cds.DisableControls();
try
extProc:=nil;
if (TMethod(cds.OnFilterRecord).Code<>nil) and (TMethod(cds.OnFilterRecord).Data<>nil) then
begin
TMethod(extProc):=TMethod(cds.OnFilterRecord);
cds.OnFilterRecord:=nil;
end;
...
... //<-- Delete Index & create new Index
...
finally
cds.OnFilterRecord:=extProc;
cds.EnableControls();
end;
一旦再次分配了事件,它就会被立即调用,并且正在迭代所有X记录,即使用户只能看到5个。
现在我正在寻找一种方法来查看记录是否已经过滤掉了,所以如果文字没有改变,我可以在我的过滤方法中跳过它。
编辑:由于要求MVCE,我将发布我的OnFilterRecord程序的简短版本。
程序完成后,计时器被禁用,控件再次启用。
procedure TDBEditFilter.FilterRecords(DataSet:TDataSet; var Accept:Boolean);
var
...
begin
//initiliaztion//
s:=FilterText; //Filtertext=User Input into the TDBEditFilters Textfield
TestFloat:=0;
Accept:=False;
/////////////////
for i:=0 to fDBGrid.Columns.Count-1 do //for all DBGrid-Columns
begin
if fDataSet.FieldByName(fDBGrid.Columns[i].FieldName).DataType in fStringTypes then
begin
Strvalue:=fDataSet.FieldByName(fDBGrid.Columns[i].FieldName).AsString;
Accept:=AnsiContainsText(Strvalue,s); //<--to ignore Upper/lowercase
end
else if fDataSet.FieldByName(fDBGrid.Columns[i].FieldName).DataType in fTimeTypes then
begin
StrValue:=DateTimeToStr(fDataSet.FieldByName(fDBGrid.Columns[i].FieldName).As DateTime,Local_Form_Settings);
Accept:=Pos(StrValue,s)<>0;
end
else if fDataSet.FieldByName(fDBGrid.Columns[i].FieldName).DataType=ftBlob then
begin
//ignore Blob
end
else //whatever fieldtype is left must be a numeric Field-type like integer or float
begin
if TryStrToFloat(s,TestFloat)=True then
begin
Accept:=(TestFloat=fDataSet.FieldByName(fDBGrid.Columns[i].FieldName).AsFloat);
end;
end;
if Accept=True then break; //stop checking this record and check next record
end;
end;
答案 0 :(得分:3)
我以为我会将此作为单独的答案发布,因为我一直在尝试 使用“过滤器TEdit”,其工作方式与我猜你的相似,但事实并非如此 似乎表现出任何特定的性能问题。我的主要假设是你每个感兴趣的数据字段使用一个过滤器TEdit,而不是用户输入一个复合类似Sql的表达式的单个过滤器TEdit,包括字段名称,比较运算符等。
我必须做出的猜测数量就是为什么我说包含MCVE对你有帮助。
我把它写成是自包含的,即它生成自己的数据而不是需要 外部数据库。
正如您将看到的,如果您尝试使用包含3000条记录的CDS, 更新过滤器的时间是几十毫秒(在我的笔记本电脑上低于20)。 如果CDS包含30000条记录,则过滤器更新时间大致呈线性增加 大约200毫秒,似乎完全可以接受gui-responsivenes pov。
(传统上,当记录数量达到数万时,TCDS被视为在性能方面取得了成功)
请注意,为简单起见
a)我没有在BirthDate或其他什么时候使用DateTime fiield, 由于处理用户输入的部分日期的复杂性。
b)在OnFilterRecord
事件中,LastName,FirstName和Age比较
通过将字段作为字符串与相应的过滤表达式进行比较来完成。
c)过滤器表达式,如果非空白,则用星号左右填充
并且使用Masks单元中的MatchesMask
函数完成值比较。
请参阅FilterExpr
。
d)IndexDef的FieldNames由其字段的名称组成 过滤器编辑的文本是非空白的。
e)如果用户快速输入几个,那么gui更新速度太慢
连续进入TEdits的字符,你可以解决这个问题
用KeyUp事件中的代码替换TEdits的OnChange事件代码
这使得TTimer的间隔为150毫秒。然后,在其OnTimer中,调用UpdateFilter
。
代码:
TForm1 = class(TForm)
DBGrid1: TDBGrid;
CDS1: TClientDataSet;
DataSource1: TDataSource;
Memo1: TMemo;
CDS1ID: TIntegerField;
CDS1Age: TIntegerField;
CDS1LastName: TStringField;
CDS1FirstName: TStringField;
edLastNameFilter: TEdit;
edFirstNameFilter: TEdit;
edAgeFilter: TEdit;
procedure CDS1FilterRecord(DataSet: TDataSet; var Accept: Boolean);
procedure edLastNameFilterChange(Sender: TObject); // Set the OnChange events for the
// FirstName and Age TEdits to this, too
procedure FormCreate(Sender: TObject);
private
procedure Log(const Title, Msg: String);
function FilterExpr(const Input: String): String;
protected
public
LastNameFilter,
FirstNameFilter,
AgeFilter : String;
IndexFields : String;
IndexDef : TIndexDef;
procedure UpdateFilterExprsAndIndex;
procedure UpdateFilter;
end;
[...]
rocedure TForm1.FormCreate(Sender: TObject);
var
i : Integer;
Ch1,
Ch2 : Char;
LastName,
FirstName : String;
Age : Integer;
begin
CDS1.CreateDataSet;
CDS1.DisableControls;
try
for i := 1 to 30000 do begin
Ch1 := Chr(Ord('a') + random(26));
Ch2 := Chr(Ord('a') + random(26));
LastName:= StringOfChar(Ch1, 1 + Random(10));
FirstName := StringOfChar(Ch2, 1 + Random(10));
Age := Trunc(Random(71));
CDS1.InsertRecord([i, LastName, FirstName, Age]);
end;
finally
CDS1.First;
CDS1.EnableControls;
end;
end;
procedure TForm1.Log(const Title, Msg : String);
begin
Memo1.Lines.Add(Title + ' : ' + Msg);
end;
procedure TForm1.CDS1FilterRecord(DataSet: TDataSet; var Accept: Boolean);
begin
Accept := True;
if LastNameFilter <> '' then
Accept := MatchesMask(CDS1LastName.AsString, LastNameFilter);
if not Accept then exit;
if FirstNameFilter <> '' then
Accept := Accept and MatchesMask(CDS1FirstName.AsString, FirstNameFilter);
if not Accept then exit;
if AgeFilter <> '' then
Accept := Accept and MatchesMask(CDS1Age.AsString, AgeFilter);
end;
procedure TForm1.edLastNameFilterChange(Sender: TObject);
begin
UpdateFilter;
end;
procedure TForm1.UpdateFilter;
var
T1 : Integer;
begin
T1 := GetTickCount;
UpdateFilterExprsAndIndex;
CDS1.DisableControls;
try
CDS1.Filtered := False;
if (edLastNameFilter.Text <> '') or (edFirstNameFilter.Text <> '') or (edAgeFilter.Text <> '') then begin
CDS1.Filtered := True;
end;
if IndexFields <> '' then
CDS1.IndexDefs[0].Fields := IndexFields; // Warning: This IndexDef needs to exist
Log('Filter update time', IntToStr(GetTickCount - T1) + 'ms');
finally
CDS1.EnableControls;
end;
end;
function TForm1.FilterExpr(const Input : String) : String;
begin
Result := Input;
if Result <> '' then
Result := '*' + Result + '*';
end;
procedure TForm1.UpdateFilterExprsAndIndex;
begin
LastNameFilter := FilterExpr(edLastNameFilter.Text);
FirstNameFilter := FilterExpr(edFirstNameFilter.Text);
AgeFilter := FilterExpr(edAgeFilter.Text);
IndexFields := '';
if LastNameFilter <> '' then
IndexFields := 'LastName';
if FirstNameFilter <> '' then begin
if IndexFields <> '' then
IndexFields := IndexFields + ';';
IndexFields := IndexFields + 'FirstName';
end;
if AgeFilter <> '' then begin
if IndexFields <> '' then
IndexFields := IndexFields + ';';
IndexFields := IndexFields + 'Age';
end;
end;
我希望这至少可以为您提供与自己比较的基础 代码,以便您可以识别任何瓶颈。
更新令我惊讶的是,我发现使用我用于测试的复合过滤器表达式,将CDS的Filter
设置为多更快表达式并使用OnFilterRecord
对其进行过滤,对于30000条记录,UpdateFilter2
小于20毫秒,而使用“UpdateFilter”的类似表达式则为200毫秒。
procedure TForm1.btnSetFilterExprClick(Sender: TObject);
begin
edFilter.Text := 'LastName=''aaa'' and FirstName = ''zz'' and Age > 30 ';
UpdateFilter2;
end;
procedure TForm1.UpdateFilter2;
var
T1 : Integer;
begin
CDS1.OnFilterRecord := Nil;
T1 := GetTickCount;
CDS1.DisableControls;
try
CDS1.Filtered := False;
CDS1.Filter := edFilter.Text;
if CDS1.Filter <> '' then begin
CDS1.Filtered := True;
end;
Log('Filter update time', IntToStr(GetTickCount - T1) + 'ms');
finally
CDS1.EnableControls;
end;
end;
答案 1 :(得分:2)
我认为你不能使用标准的TClientDataset实现这样做 索引和过滤。
对TCDS上的索引或过滤器的更改都会调用其数据记录的遍历 并且你无法控制它,因为在这两种情况下,TCDS功能都依赖于 调用Midas.Dll提供的接口。
设置新的或更改的索引涉及调用procedure TCustomClientDataSet.SortOnFields
反过来调用Cursor.SortOnFields
,其中Cursor
的类型为IDSCursor - 请参阅DSIntf.Pas
同样,更改CDS过滤器涉及调用TCustomClientDataSet.AddExprFilter
,其中包含FDSCursor.AddFilter
转而调用FDSCursor
,其中onCreate(savedInstanceState)
的类型为IDSCursor。
因此,您需要在Midas级别重新实现这两个方面 IDSCursor interfave以避免默认行为。