2018-03-20

VirtualStringTree Filter Node

VirtualStringTree 過濾資料

過濾所有的 Node 當然就會依據不同需求而有不同的做法。這裡紀錄的是我在過濾 Node 學到的問題。
這裡用到的程式碼在上一段 [ VirtualStringTree Load DataSet - Event ] 實作。

A.開始發起

要來先從過濾的發起開始。A1 是最初認為的版本,開始過濾資料前後使用 BeginUpdate, EndUpdate。
procedure TfrmFilterCust.TimerFilterTimer(Sender: TObject);
var
  vDT: TDatetime;
  vStr: String;
begin
  TimerFilter.Enabled := False;
  vStr := Big5ToCn(edtFilter.Text);
  vDT := Now;

  //vst.BeginUpdate;
  Self.Cursor := crHourGlass;
  FilterNode(vStr);
  Self.Cursor := crDefault;
  //vst.EndUpdate;

  labRec.Caption := CurrToDecimal(vst.VisibleCount, 0) + '.';
  labTest.Caption :=
    CurrToDecimal((MilliSecondsBetween(Now, vDT)/1000), 3) + '/s';

  if FSeledNode <> nil then
    vst.ScrollIntoView(FSeledNode, True);
end;


B.過濾遞迴

以下的 FilterNode 是整個過濾資料的主遞迴程式,過濾的條件不說明。B1 這裡的 Root Node 是不顯示的。

最初認為的版本是有 A1 的 BeginUpdate, EndUpdate,而沒有 B2 的。這會在需要展開的時候呼叫 Expanded,但他只會觸動多個 OnInitChildren。注意的是多個 OnInitChildren 觸發完之後,回到 A1 的 EndUpdate 才觸動累積的 OnInitNode。這時候的 OnInitNode 所取得的 DataSet 永遠是最後一個 OnInitChildren 所留下的 Cursor。
function TfrmFilterCust.FilterNode(const aSubStr: string;
  ParentNode: PVirtualNode): Integer;
var
  vPosIdx: Integer;
  vCnStr: String;
  vIsRoot, vIsFound, vIsC ,vIsS: Boolean;
  vANode, vANode_up: PCustNode;
  vParentNode : PVirtualNode;
begin
  Result := 0;
  vIsRoot := (ParentNode = nil);
  if vIsRoot then
  begin
    vParentNode := vst.RootNode.FirstChild;  // B1
    vANode_up := nil;
  end
  else
  begin
    vParentNode := ParentNode.FirstChild;
    vANode_up := ParentNode.GetData;
  end;

  while vParentNode <> nil do
  begin
    vIsFound := (aSubStr = '');
    // 如 Parent 符合, Child 全部顯示 // VisibleChildNodes
    // 如 B Child 符合, Parent 顯示, 但 Other Child 不顯示
    vANode := vParentNode.GetData;
    if not vIsFound then // CustID: 交易商代碼;
    begin
      vCnStr := vANode.CustID;
      vPosIdx := Pos(aSubStr, vCnStr);

      if vPosIdx = 0 then
        vIsFound := False
      else if chkFilterL.Checked then //字首查詢
        vIsFound := vPosIdx = 1
      else if chkFilterR.Checked then //字尾查詢
        vIsFound := vPosIdx = Length(vCnStr)-Length(aSubStr)+1
      else
        vIsFound := vPosIdx > 0;
    end;
    if not vIsFound then // ShortName: 交易商簡稱;
    begin
      vCnStr := Big5ToCn(vANode.ShortName);
      vPosIdx := Pos(aSubStr, vCnStr);

      if vPosIdx = 0 then
        vIsFound := False
      else if chkFilterL.Checked then //字首查詢
        vIsFound := vPosIdx = 1
      else if chkFilterR.Checked then //字尾查詢
        vIsFound := vPosIdx = Length(vCnStr)-Length(aSubStr)+1
      else
        vIsFound := vPosIdx > 0;
    end;
    if not vIsFound then // FullName: 交易商全名;
    begin
      vCnStr := Big5ToCn(vANode.FullName);
      vPosIdx := Pos(aSubStr, vCnStr);

      if vPosIdx = 0 then
        vIsFound := False
      else if chkFilterL.Checked then //字首查詢
        vIsFound := vPosIdx = 1
      else if chkFilterR.Checked then //字尾查詢
        vIsFound := vPosIdx = Length(vCnStr)-Length(aSubStr)+1
      else
        vIsFound := vPosIdx > 0;
    end;

    if vANode_up = nil then
    begin
      vIsC := IIF(chkIsCustomer.Checked, vANode.IsCustomer, True);
      vIsS := IIF(chkIsSupplier.Checked, vANode.IsSupplier, True);
    end
    else
    begin // 如 Parent 不符合,Child 符合,就符合條件
      vIsC := IIF(chkIsCustomer.Checked, (vANode.IsCustomer or vANode_up.IsCustomer), True);
      vIsS := IIF(chkIsSupplier.Checked, (vANode.IsSupplier or vANode_up.IsSupplier), True);
    end;

    vIsFound := vIsFound and vIsC and vIsS;

    if vIsFound then
    begin
      vst.IsVisible[vParentNode] := True;
      Inc(Result);
      if vParentNode.ChildCount > 0 then
        VisibleChildNode(vParentNode);
    end
    else
    begin
      if vANode.ChildCount > 0 then
      begin
        if vParentNode.ChildCount = 0 then
        begin
          vst.BeginUpdate;  // B2
          vst.Expanded[vParentNode] := True;
          vst.EndUpdate;  // B2
        end;

        if FilterNode(aSubStr, vParentNode) > 0 then
        begin
          // 如果只有這一行,
          // 是 Parent 沒有,Child A 有,只顯示 Parent & Child A
          vst.IsVisible[vParentNode] := True;

          // 如 Parent 沒有,Child B 有,顯示 Parent & Child 全部
          VisibleChildNode(vParentNode);

          Inc(Result);
        end
        else
          vst.IsVisible[vParentNode] := False;
      end
      else
        vst.IsVisible[vParentNode] := False;
    end;

    vParentNode := vParentNode.NextSibling;
  end;
end;

速度

17.38秒:
挑一個過濾所展開符合條件的Parent,發現慢的還是 DataSet下 Filter 與 Load 資料的部分。
03.18秒:
如果上一段的測試都已經把 DataSet Load 到 Record 之後,後續的重新過濾 Node 就快多了。

範例程式:
test_FilterCust.7z

[不要忘記 Filter & VirtualStringTree / VirtualTreeView]

沒有留言: