Übergeordnetes Element von RootNode von TVirtualStringTree

Sep 18 2020

Gemäß der Dokumentation ist die Eigenschaft RootNode von TVirtualStringTree ein versteckter Knoten, der allen vom Benutzer erstellten Knoten übergeordnet ist. Das übergeordnete Element des RootNode ist jedoch festgelegt. Ich stehe vor folgendem Problem:

Manchmal wird beim Schließen eines Formulars mit einem TVirtualStringTree ein Fehler bei der Zugriffsverletzung angezeigt. Beim Debuggen tritt der Fehler auf, wenn versucht wird, auf die Name-Eigenschaft der Komponente (virtualstringtree) zuzugreifen. Wenn ich versuche, die Name-Eigenschaft auszuwerten, erhalte ich den Wert "Unzugänglicher Wert". Als ich das Problem untersuchte, stellte ich fest, dass die Name-Eigenschaft einer Komponente zur Laufzeit nicht geändert werden kann :

Warnung: Wenn Sie den Namen zur Laufzeit ändern, werden alle Verweise auf den alten Namen undefiniert. Jeder nachfolgende Code, der den alten Namen verwendet, verursacht eine Ausnahme.

Es ist passiert, dass ich dies in meinem Code nicht ändere. Bei Verwendung eines Daten-Haltepunkts in der Name-Eigenschaft habe ich festgestellt, dass er sich in einer virtuellen Stringtree-Schleife ändert (Beispielcode, den ich in einer Beispiel-App zum Testen des Problems verwende, der Produktionscode ist jedoch ähnlich):

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

Die GetNodeData sind die folgenden:

 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;

Geben Sie, dass ich den folgenden Baum habe:

Node 1
  Node 2
    Node 3

Wenn der Knoten 3 ausgewählt ist, wird die Schleife fünfmal ausgeführt. 3 in meinen erstellten Knoten, dann der RootNode (versteckt) und dann der Parent der Root. Nur das übergeordnete Element des übergeordneten Stammknotens ist null. In der GetNodeData-Methode wird überprüft, ob es sich um den Stammknoten handelt, und es wird null zurückgegeben. Da es sich bei diesem übergeordneten Element des Stamms jedoch nicht um den Stamm handelt, wird der Code "else" eingegeben. Hier ist das Problem:

Die Adresse der Name-Eigenschaft des virtualstrintree lautet $ 141B9768

Die Adresse der States-Eigenschaft des übergeordneten Elements des Stammknotens lautet $ 141B976A

Wenn Sie den Daten-Haltepunkt in die Name-Eigenschaft einfügen, wird er an dieser Stelle geändert:

Include(Node.States, vsOnFreeNodeCallRequired);

und Erzeugen des AV.

Ich weiß, dass ich die Schleife ändern kann, um den Stammknoten korrekt zu überprüfen , aber ich möchte dies verstehen, also behebe ich möglicherweise die Komponente (wenn es sich um einen Komponentenfehler handelt) anstelle meines Codes.

Code:

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;

Fügen Sie einfach einen Baum und eine Schaltfläche in das Formular ein, ich setze die Schleife in Klick auf die Schaltfläche.

Antworten

2 TomBrunberg Sep 18 2020 at 14:19

Zunächst die Dokumentation: (zB) https://documentation.help/VirtualTreeview/TBaseVirtualTree_RootNode.html

sagt (Hervorhebung von mir)

Eigenschaft RootNode: PVirtualNode;

Beschreibung

Zur Verankerung der Baumhierarchie wird ein interner Baumknoten gepflegt, der meist wie jeder andere Baumknoten ist, aber manchmal anders gehandhabt wird. Der Wurzelknoten wird immer erweitert und initialisiert. Das übergeordnete Element zeigt auf die Baumansicht, zu der der Knoten gehört, und die Elemente PreviousSibling und NextSibling verweisen auf den Stammknoten selbst, damit dieser Knoten tatsächlich erkannt werden kann .

Anmerkungen

Sie sollten den Stammknoten nicht zum Durchlaufen des Baums verwenden. Es ist nur öffentlich zugänglich, da es das übergeordnete Element aller Knoten der obersten Ebene ist und zum Testen eines Knotens verwendet werden kann, ob es sich um einen Knoten der obersten Ebene handelt oder nicht.

Zweitens

In deinem Code

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

Sie fahren bedingungslos am internen Wurzelknoten vorbei nach oben, weil Sie nicht prüfen P, ob es sich um die interne Wurzel handelt, indem Sie mit tree.RootNode(oder mit P.NextSibling) vergleichen.

Vielleicht ändern Sie die

while Assigned(P) do

Bedingung zu

while P <> tree.RootNode do

würde zu dir passen

1 RodrigoCaetano Sep 18 2020 at 19:57

Dank Toms Antwort und der Dokumentation der Komponente ist das übergeordnete Element von RootNode tatsächlich der Baum selbst. Wenn Sie also die Schleife mit dem Parent = nilas-Punkt des Stopps erstellen, wenn Sie die Wurzel (den Baum) des übergeordneten Elements erreichen , wird der Code Includein GetNodeDataausgeführt, und in meinem Fall lag die StateAdresse beim Zugriff auf die Eigenschaft sehr nahe an der Namensadresse des Baums , weil die Adresse von "Knoten" der Baum war.

Also repariere ich die Schleife, nach der gesucht werden soll Node <> Tree.RootNode und auch die fix GetNodeDataauch zu überprüfen , ob Knoten Selbst ist: or (Node = Pointer(Self), nur für den Fall anderen Code in meiner Anwendung (legacy von 2.000.000 + Code) verwendet die gleiche falsche Schleife :

Vor:

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;

Nach dem:

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;