Element nadrzędny RootNode TVirtualStringTree

Sep 18 2020

Zgodnie z dokumentacją, właściwość RootNode TVirtualStringTree jest ukrytym węzłem, nadrzędnym dla wszystkich węzłów utworzonych przez użytkowników. Ale element nadrzędny RootNode jest ustawiony. Mam następujący problem:

Czasami podczas zamykania formularza zawierającego TVirtualStringTree pojawia się błąd naruszenia dostępu. Debugowanie, błąd występuje podczas próby uzyskania dostępu do właściwości Name komponentu (virtualstringtree). Jeśli spróbuję ocenić właściwość Nazwa, otrzymuję komunikat „Niedostępna wartość”. Zagłębiając się w problem, odkryłem, że właściwości Nazwa komponentu nie można zmienić w czasie wykonywania :

Ostrzeżenie: zmiana nazwy w czasie wykonywania powoduje, że wszelkie odwołania do starej nazwy stają się niezdefiniowane. Każdy kolejny kod, który używa starej nazwy, spowoduje wyjątek.

Zdarzyło się, że nie zmieniam tego w swoim kodzie. Korzystając z punktu przerwania danych we właściwości Name, zauważyłem, że zmienia się on w pętli virtualstringtree, coś takiego (przykładowy kod, którego używam w przykładowej aplikacji do testowania problemu, ale kod produkcyjny jest podobny):

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

GetNodeData jest następujący:

 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;

Dając, że mam następujące drzewo:

Node 1
  Node 2
    Node 3

Po wybraniu węzła 3 pętla jest wykonywana 5 razy. 3 w utworzonych przeze mnie węzłach, następnie RootNode (ukryty), a następnie Parent of the root. Tylko rodzic macierzystego węzła głównego jest zerowy. W metodzie GetNodeData jest weryfikowana czy jest to węzeł główny, zwraca nil. Ale dla tego rodzica katalogu głównego, ponieważ nie jest on korzeniem, wprowadza kod else. Teraz jest problem:

Adres właściwości Name w virtualstrintree to $ 141B9768

Adres właściwości States elementu nadrzędnego węzła głównego to $ 141B976A

Podczas umieszczania punktu przerwania danych we właściwości Name, zmienia się on w tym momencie:

Include(Node.States, vsOnFreeNodeCallRequired);

i generowanie pliku AV.

Wiem, że mogę zmienić pętlę, aby poprawnie sprawdzić węzeł główny , ale chcę to zrozumieć, więc może naprawię komponent (jeśli jest to błąd komponentu) zamiast mojego kodu.

Kod:

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;

Po prostu umieść drzewo i przycisk w formularzu, wstawiam pętlę w kliknięcie przycisku.

Odpowiedzi

2 TomBrunberg Sep 18 2020 at 14:19

Najpierw dokumentacja: (np.) https://documentation.help/VirtualTreeview/TBaseVirtualTree_RootNode.html

mówi (podkreślenie moje)

właściwość RootNode: PVirtualNode;

Opis

W celu zakotwiczenia hierarchii drzewa utrzymywany jest wewnętrzny węzeł drzewa, który jest w większości taki sam, jak każdy inny węzeł drzewa, ale czasami jest inaczej obsługiwany. Węzeł główny jest zawsze rozwijany i inicjowany. Jego element nadrzędny wskazuje na widok drzewa, do którego należy węzeł, a jego elementy członkowskie PreviousSibling i NextSibling wskazują na sam węzeł główny, aby umożliwić faktyczne rozpoznanie tego węzła .

Uwagi

Nie powinieneś używać węzła głównego do iteracji po drzewie. Jest dostępny publicznie tylko dlatego, że jest nadrzędny dla wszystkich węzłów najwyższego poziomu i może być używany do testowania węzła, czy jest to węzeł najwyższego poziomu, czy nie.

Po drugie

W twoim kodzie

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

bezwarunkowo przechodzisz w górę poza wewnętrzny węzeł główny, ponieważ nie sprawdzasz, czy Pjesteś wewnętrznym korzeniem, porównując z tree.RootNode(lub porównując z P.NextSibling).

Być może zmiana

while Assigned(P) do

warunek do

while P <> tree.RootNode do

pasowałoby do ciebie

1 RodrigoCaetano Sep 18 2020 at 19:57

Tak więc, dzięki odpowiedzi Toma i dokumentacji komponentu, rodzicem RootNode jest właściwie samo drzewo. Tak więc, podczas wykonywania pętli z Parent = niljako punktu zatrzymania, po osiągnięciu korzeń rodzica (drzewo), kod Includew GetNodeDatajest wykonywany, aw moim przypadku, gdy dostęp do Statenieruchomości, jej adres był bardzo blisko Nazwa adresu treee , ponieważ adresem „węzła” było drzewo.

Tak, ja naprawić pętlę, aby sprawdzić, Node <> Tree.RootNodea także naprawić GetNodeDatarównież sprawdzić, czy węzeł jest samodzielne: or (Node = Pointer(Self), tylko w przypadku innego kodu w mojej aplikacji (spuściznę 2 mln + kod) używa tego samego złego pętlę :

Przed:

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;

Po:

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;