TVirtualStringTreeのRootNodeの親

Sep 18 2020

ドキュメントによると、TVirtualStringTreeのプロパティRootNodeは隠れノードであり、ユーザーが作成したすべてのノードの親です。ただし、RootNodeの親は設定されています。私は次の問題に直面しています:

TVirtualStringTreeが含まれているフォームを閉じると、アクセス違反エラーが発生することがあります。デバッグ中、コンポーネント(virtualstringtree)のNameプロパティにアクセスしようとするとエラーが発生します。Nameプロパティを評価しようとすると、「アクセスできない値」が表示されます。問題を掘り下げてみると、コンポーネントのNameプロパティは実行時に変更できないことがわかりました。

警告:実行時に名前を変更すると、古い名前への参照が未定義になります。古い名前を使用する後続のコードは例外を引き起こします。

たまたま、コードでこれを変更していません。Nameプロパティでデータブレークポイントを使用すると、virtualstringtreeループで次のように変化していることがわかりました(サンプルアプリで問題をテストするために使用しているサンプルコードですが、本番コードは似ています)。

var
  N: PNode;
  P: PVirtualNode;
begin
  P := tree.FocusedNode;
  while Assigned(P) do
  begin
    N := tree.GetNodeData(P);
    P := P.Parent;
  end;

GetNodeDataは次のとおりです。

 if (FNodeDataSize <= 0) or (Node = nil) or (Node = FRoot) then
    Result := nil
  else begin
    Result := PByte(@Node.Data) + FTotalInternalDataSize;
    Include(Node.States, vsOnFreeNodeCallRequired); // We now need to call OnFreeNode, see bug #323
  end;

私が次のツリーを持っているとすると:

Node 1
  Node 2
    Node 3

ノード3を選択すると、ループが5回実行されます。作成したノードで3つ、次にRootNode(非表示)、次にルートの親。親ルートノードの親のみがnilです。GetNodeDataメソッドでは、ルートノードであるかどうかが検証され、nilが返されます。ただし、ルートのこの親の場合、ルートではないため、elseコードを入力します。ここに問題があります:

virtualstrintreeのNameプロパティのアドレスは$ 141B9768です

ルートノードの親のStatesプロパティのアドレスは$ 141B976Aです。

Nameプロパティにデータブレークポイントを設定すると、この時点で変更されます。

Include(Node.States, vsOnFreeNodeCallRequired);

AVを生成します。

ルートノードを正しくチェックするためにループを変更できることはわかっていますが、これを理解したいので、コードではなくコンポーネントを修正する可能性があります(コンポーネントのバグの場合)。

コード:

type
  TNode = record
    Text: string;
  end;
  PNode = ^TNode;
...
procedure TForm1.FormCreate(Sender: TObject);
var
  N1, N2, N3: TNode;
  TreeNode: PVirtualNode;
begin
  N1.Text := 'Node 1';
  TreeNode := tree.AddChild(nil, PNode(N1));

  N2.Text := 'Node 2';
  TreeNode := tree.AddChild(TreeNode, PNode(N2));

  N3.Text := 'Node 3';
  TreeNode := tree.AddChild(TreeNode, PNode(N3));
end;

フォームにツリーとボタンを配置するだけで、ボタンをクリックするだけでループを配置します。

回答

2 TomBrunberg Sep 18 2020 at 14:19

まず、ドキュメント:(eg) https://documentation.help/VirtualTreeview/TBaseVirtualTree_RootNode.html

言う(私の強調)

プロパティRootNode:PVirtualNode;

説明

ツリー階層を固定するために、内部ツリーノードが維持されます。これは、他のツリーノードとほとんど同じですが、処理が異なる場合があります。ルートノードは常に展開され、初期化されます。その親メンバーはノードが属するツリービューを指し、そのPreviousSiblingおよびNextSiblingメンバーはルートノード自体を指し、このノードを実際に認識できるようにします。

ノート

ツリーを反復処理するためにルートノードを使用しないでください。これは、すべてのトップレベルノードの親であり、ノードがトップレベルノードであるかどうかをテストするために使用できるため、パブリックにのみアクセスできます。

第二に

あなたのコードで

var
  N: PNode;
  P: PVirtualNode;
begin
  P := tree.FocusedNode;
  while Assigned(P) do
  begin
    N := tree.GetNodeData(P);
    P := P.Parent;
  end;

とP比較するtree.RootNode(またはと比較するP.NextSibling)ことによって内部ルートであるかどうかをチェックしていないため、無条件に内部ルートノードを超えて上向きにトラバースしています。

おそらく変更する

while Assigned(P) do

条件

while P <> tree.RootNode do

あなたに合うだろう

1 RodrigoCaetano Sep 18 2020 at 19:57

したがって、トムの回答とコンポーネントのドキュメントのおかげで、RootNodeの親は実際にはツリー自体です。ループ作るときに、Parent = nil親のルート(木)に達したときに停止する点として、コードIncludeでは、GetNodeData私の場合には、実行されにアクセスするとき、Stateプロパティを、そのアドレスはtreeeの名前アドレスに非常に近かったです、「ノード」のアドレスがツリーだったため。

だから、私はチェックするループをNode <> Tree.RootNode修正し、GetNodeDataノードが自己であるかどうかもチェックするように修正します:or (Node = Pointer(Self)、私のアプリケーションの他のコード(200万以上のコードのレガシー)が同じ間違ったループを使用する場合に備えて:

前:

function TBaseVirtualTree.GetNodeData(Node: PVirtualNode): Pointer;
begin
  Assert(FNodeDataSize > 0, 'NodeDataSize not initialized.');
  if (FNodeDataSize <= 0) or (Node = nil) or (Node = FRoot) then
    Result := nil
  else begin
    Result := PByte(@Node.Data) + FTotalInternalDataSize;
    Include(Node.States, vsOnFreeNodeCallRequired); // We now need to call OnFreeNode, see bug #323
  end;
end;

後:

function TBaseVirtualTree.GetNodeData(Node: PVirtualNode): Pointer;
begin
  Assert(FNodeDataSize > 0, 'NodeDataSize not initialized.');
  if (FNodeDataSize <= 0) or (Node = nil) or (Node = FRoot) or (Node = Pointer(Self)) then
    Result := nil
  else begin
    Result := PByte(@Node.Data) + FTotalInternalDataSize;
    Include(Node.States, vsOnFreeNodeCallRequired); // We now need to call OnFreeNode, see bug #323
  end;
end;