Delphi:检查DataSet的Record是可见还是已过滤

时间:2017-03-20 11:20:18

标签: delphi filter dataset

在工作中,我们有一个名为" 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程序的简短版本。

  • 每次组件未收到输入1秒钟时执行以下步骤
  • fStringtypes和fTimeTypes都是一组TFieldType
  • fStringTypes:= [ftString,ftMemo,ftFMTMemo,ftFixedChar,ftWideString];
  • fTimeTypes:= [ftDate,ftTime,ftDateTime,ftTimeStamp];
  • 程序完成后,计时器被禁用,控件再次启用。

    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;
    

2 个答案:

答案 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以避免默认行为。