3

パフォーマンス上の理由から、再帰を使用せずにツリービューのアイテムを参照する必要があります。

TTreeview は GlobalCount メソッドと ItemByGlobalIndex メソッドを提供しますが、可視アイテムのみを返します
。すべてのノードのプライベート リストを見つけることなく、ルート クラス コードを検索しました。FGlobalItems はレンダリングが必要なアイテムのみを保持しているようです。

ツリービューのすべてのアイテム (表示されていないノードや折りたたまれたノードを含む) を順番に参照する方法はありますか?

この質問は Delphi XE3 / FM2 に適用されます

ありがとう、

[2 月 3 日編集]
この側面で firemonkey ツリービューにパッチを適用する方法を探していたにもかかわらず、デフォルトの回答 (そのままでは使用できません) を受け入れました。
さらに分析した結果、FGlobalItems リストは展開された項目のみを保持し、メソッド TCustomTreeView.UpdateGlobalIndexes で維持されていることがわかりました。
FMX.TreeView の 924 行目にコメントを付ける (if AItem.IsExpanded then...) と、ノードの完全なインデックスが構築され、ItemByGlobalIndex() を使用してすべてのノードを順番に参照できるようになりますが、他のパフォーマンスの問題やバグが発生する可能性があります...
これ以上の手がかりがないので、再帰コードを保持します。

4

6 に答える 6

3

質問は本質的に、再帰なしでツリーをトラバースする方法を尋ねます。ツリーをたどる方法はたくさんあります。ツリーがたまたまビジュアル コントロールのノードで表されているという事実は関係ありません。

一部のアルゴリズムでは、走査を再帰的に考える方が簡単です。このようにして、現在アクティブなノードをスタック上の引数として保持することにより、プログラミング言語がツリー内のどこにいるかを追跡できるようにします。再帰を使用したくない場合は、進行状況を自分で追跡する必要があります。そのための一般的なツールには、スタックとキューが含まれます。

preorder トラバーサルとは、ノードにアクセスしたときに、ノードの子に対してアクションを実行する前に、そのノードのデータに対してアクションを実行することを意味します。これは、ツリー ビュー コントロールの各ノードを上から下に移動することに対応します。スタックを使用して次のように実装できます。

procedure PreorderVisit(Node: TTreeNode; Action: TNodeAction);
var
  Worklist: TStack<TTreeNode>;
  i: Integer;
begin
  Worklist := TStack<TTreeNode>.Create;
  try
    Worklist.Push(Node);
    repeat
      Node := Worklist.Pop;
      for i := Pred(Node.Items.Count) downto 0 do
        Worklist.Push(Node.Items[i]);
      Action(Node);
    until Worklist.Empty;
  finally
    Worklist.Free;
  end;
end;

子を逆の順序でスタックにプッシュして、希望の順序でポップされるようにします。

そのコードでActionは、各ノードで実行する必要があるタスクを表します。コードで指定されているように外部関数として使用するかPreorderVisit、タスク固有のコードを含む特殊なバージョンの を作成することができます。

ただし、TTreeView は実際にはtreeを表していません。まさに(木の集まり)です。これは、ルートを表す単一のノードがないためです。ただし、上記の関数を使用してツリー内のすべてのノードを簡単に処理できます。

procedure PreorderVisitTree(Tree: TTreeView; Action: TNodeAction);
var
  i: Integer;
begin
  for i := 0 to Pred(Tree.Items.Count) do
    PreorderVisit(Tree.Items[i], Action);
end;

TTreeView の特定の構造を利用して事前注文トラバーサルを行う別の方法はGetNext、各ノードの組み込みメソッドを使用することです。

procedure PreorderVisitTree(Tree: TTreeView; Action: TNodeAction);
var
  Node: TTreeNode;
begin
  if Tree.Items.Count = 0 then
    exit;
  Node := Tree.Items[0];
  repeat
    Action(Node);
    Node := Node.GetNext;
  until not Assigned(Node);
end;

Firemonkey ツリー ビューの非表示ノードを取得する方法はないようです。GUI から情報を抽出しようとするのではなく、内部ツリー データ構造を反復処理することで、より良い結果が得られる場合があります。

于 2013-02-01T17:43:09.487 に答える
3

非再帰的な方法でツリービューを歩くための私の関数は次のとおりです。ノードがあり、ツリー全体をたどる必要なく次または前のノードに移動したい場合に使用するのは簡単です。

GetNextItem は、最初の子を参照するか、子がない場合は、次の子の親を参照します (必要に応じて親をさらに調べます)。

GetPrevItem は親を調べて前のアイテムを見つけ、GetLastChild を使用してそのアイテムの最後の子を見つけます (再帰を使用します)。

記述されたコードは Expanded ノードのみをウォークしますが、すべてのノードをウォークするように簡単に変更できます (IsExpanded への参照を削除するだけです)。

function GetLastChild(Item: TTreeViewItem): TTreeViewItem;
begin
  if (Item.IsExpanded) and (Item.Count > 0) then
    Result := GetLastChild(Item.Items[Item.Count-1])
  else
    Result := Item;
end;

function GetNextItem(Item: TTreeViewItem): TTreeViewItem;
var ItemParent: TTreeViewItem;
  I: Integer;
  TreeViewParent: TTreeView;
  Parent: TFMXObject;
  Child: TFMXObject;
begin
  if Item = nil then
    Result := nil
  else if (Item.IsExpanded) and (Item.Count > 0) then
    Result := Item.Items[0]
  else
  begin
    Parent := Item.Parent;
    Child := Item;
    while (Parent <> nil) and not (Parent is TTreeView) do
    begin
      while (Parent <> nil) and not (Parent is TTreeView) and not (Parent is TTreeViewItem) do
        Parent := Parent.Parent;

      if (Parent <> nil) and (Parent is TTreeViewItem) then
      begin
        ItemParent := TTreeViewItem(Parent);
        I := 0;
        while (I < ItemParent.Count) and (ItemParent.Items[I] <> Child) do
          inc(I);
        inc(I);
        if I < ItemParent.Count then
        begin
          Result := ItemParent.Items[I];
          EXIT;
        end;
        Child := Parent;
        Parent := Parent.Parent
      end;
    end;

    if (Parent <> nil) and (Parent is TTreeView) then
    begin
      TreeViewParent := TTreeView(Parent);
      I := 0;
      while (I < TreeViewParent.Count) and (TreeViewParent.Items[I] <> Item) do
        inc(I);
      inc(I);
      if I < TreeViewParent.Count then
        Result := TreeViewParent.Items[I]
      else
      begin
        Result := Item;
        EXIT;
      end;
    end
    else
      Result := Item
  end
end;

function GetPrevItem(Item: TTreeViewItem): TTreeViewItem;
var Parent: TFMXObject;
  ItemParent: TTreeViewItem;
  TreeViewParent: TTreeView;
  I: Integer;
begin
  if Item = nil then
    Result := nil
  else
  begin
    Parent := Item.Parent;
    while (Parent <> nil) and not (Parent is TTreeViewItem) and not (Parent is TTreeView) do
      Parent := Parent.Parent;

    if (Parent <> nil) and (Parent is TTreeViewItem) then
    begin
      ItemParent := TTreeViewItem(Parent);
      I := 0;
      while (I < ItemParent.Count) and (ItemParent.Items[I] <> Item) do
        inc(I);
      dec(I);
      if I >= 0 then
        Result := GetLastChild(ItemParent.Items[I])
      else
        Result := ItemParent;
    end
    else if (Parent <> nil) and (Parent is TTreeView) then
    begin
      TreeViewParent := TTreeView(Parent);
      I := 0;
      while (I < TreeViewParent.Count) and (TreeViewParent.Items[I] <> Item) do
        inc(I);
      dec(I);
      if I >= 0 then
        Result := GetLastChild(TreeViewParent.Items[I])
      else
        Result := Item
    end
    else
      Result := Item;
  end;
end;
于 2013-02-03T11:53:36.533 に答える
1

Item.ParentItemnil も可能です。Parent := Item.ParentItemそのため、行を次の行に置き換えました。

  if Item.ParentItem <> nil then
    Parent := Item.ParentItem
  else
    Parent := Item.TreeView;

GetNextItem修正後の完全な機能:

function GetNextItem(Item: TTreeViewItem): TTreeViewItem;
var
  Parent: TFMXObject;
  Child: TTreeViewItem;
begin
  Result := nil;
  if Item.Count > 0 then
    Result := Item.Items[0]
  else begin
    if Item.ParentItem <> nil then
      Parent := Item.ParentItem
    else
      Parent := Item.TreeView;
    Child := Item;
    while (Result = nil) and (Parent <> nil) do
    begin
      if Parent is TTreeViewItem then
      begin
        if TTreeViewItem(Parent).Count > (Child.Index + 1) then
          Result := TTreeViewItem(Parent).Items[Child.Index + 1]
        else begin
          Child := TTreeViewItem(Parent);
          if Child.ParentItem <> nil then
            Parent := Child.ParentItem
          else
            Parent := Child.TreeView;
        end;
      end else begin
        if TTreeView(Parent).Count > Child.Index + 1 then
          Result := TTreeView(Parent).Items[Child.Index + 1]
        else
          Parent := nil;
      end;
    end;
  end;
end;

Delphi 10.3.2 でテスト済み

于 2019-08-20T14:32:47.920 に答える
1

XE8では、これは私にとってはうまくいきます:

function GetNextItem(Item: TTreeViewItem): TTreeViewItem;
var
   Parent: TFMXObject;
   Child: TTreeViewItem;
begin
    Result := nil;
    if Item.Count > 0 then
        Result := Item.Items[0]
    else
    begin
        Parent := Item.ParentItem;
        Child := Item;
        while (Result = nil) and (Parent <> nil) do
        begin
           if Parent is TTreeViewItem then
           begin
               if TTreeViewItem(Parent).Count > (Child.Index + 1) then
                   Result := TTreeViewItem(Parent).Items[Child.Index + 1]
               else
               begin
               Child := TTreeViewItem(Parent);
               if Child.ParentItem <> nil then
                   Parent := Child.ParentItem
               else
                   Parent := Child.TreeView;
               end;
           end
           else
           begin
            if TTreeView(Parent).Count > Child.Index + 1 then
                Result := TTreeView(Parent).Items[Child.Index + 1]
            else
                Parent := nil;
            end;
        end;
    end;
end;
于 2015-12-02T10:41:10.260 に答える