Genitore di RootNode di TVirtualStringTree
Secondo la documentazione, la proprietà RootNode di TVirtualStringTree è un nodo nascosto, genitore di tutti i nodi creati dall'utente. Ma il genitore del RootNode è impostato. Sto affrontando il seguente problema:
A volte, quando si chiude un modulo che contiene un TVirtualStringTree, ottengo un errore di violazione di accesso. Debug, l'errore si verifica quando si cerca di accedere alla proprietà Name del componente (virtualstringtree). Se provo a valutare la proprietà Name, ottengo "Valore inaccessibile". Approfondendo il problema, ho scoperto che la proprietà Name di un componente non può essere modificata in runtime :
Avvertenza: la modifica del nome in fase di esecuzione fa sì che tutti i riferimenti al vecchio nome diventino indefiniti. Qualsiasi codice successivo che utilizza il vecchio nome causerà un'eccezione.
È successo che non lo sto cambiando nel mio codice. Utilizzando un punto di interruzione dei dati nella proprietà Name, ho visto che stava cambiando in un ciclo virtualstringtree, qualcosa di simile (codice di esempio che sto usando in un'app di esempio per testare il problema, ma il codice di produzione è simile):
var
N: PNode;
P: PVirtualNode;
begin
P := tree.FocusedNode;
while Assigned(P) do
begin
N := tree.GetNodeData(P);
P := P.Parent;
end;
Il GetNodeData è il seguente:
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;
Dando che ho il seguente albero:
Node 1
Node 2
Node 3
Quando è selezionato il nodo 3, il ciclo viene eseguito 5 volte. 3 nei miei nodi creati, quindi il RootNode (nascosto) e quindi il genitore della radice. Solo il genitore del nodo radice genitore è nullo. Nel metodo GetNodeData viene verificato se è il nodo radice, restituisce nil. Ma per questo genitore della radice, poiché non è la radice, entra nel codice else. Ora ecco il problema:
L'indirizzo della proprietà Name del virtualstrintree è $ 141B9768
L'indirizzo della proprietà States del genitore del nodo radice è $ 141B976A
Quando si inserisce il punto di interruzione dei dati nella proprietà Name, viene modificato a questo punto:
Include(Node.States, vsOnFreeNodeCallRequired);
e generare l'AV.
So di poter cambiare il ciclo, per controllare correttamente il nodo radice , ma voglio capirlo, quindi forse aggiusto il componente (se si tratta di un bug del componente), invece del mio codice.
Codice:
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;
Basta inserire un albero e un pulsante nel modulo, ho messo il loop nel clic del pulsante.
Risposte
Innanzitutto, la documentazione: (ad esempio) https://documentation.help/VirtualTreeview/TBaseVirtualTree_RootNode.html
dice (enfasi mia)
proprietà RootNode: PVirtualNode;
Descrizione
Per ancorare la gerarchia ad albero viene mantenuto un nodo ad albero interno che è per lo più proprio come qualsiasi altro nodo ad albero, ma a volte è stato gestito diversamente. Il nodo radice viene sempre espanso e inizializzato. Il suo membro padre punta alla visualizzazione ad albero a cui appartiene il nodo ei suoi membri PreviousSibling e NextSibling puntano al nodo radice stesso per rendere possibile il riconoscimento effettivo di questo nodo .
Appunti
Non dovresti usare il nodo radice per iterare attraverso l'albero. È accessibile solo pubblicamente perché è il genitore di tutti i nodi di primo livello e può essere utilizzato per testare un nodo che si tratti di un nodo di primo livello o meno.
In secondo luogo
Nel tuo codice
var
N: PNode;
P: PVirtualNode;
begin
P := tree.FocusedNode;
while Assigned(P) do
begin
N := tree.GetNodeData(P);
P := P.Parent;
end;
stai attraversando incondizionatamente verso l'alto oltre il nodo radice interno perché non stai verificando di Pessere la radice interna, confrontandolo con tree.RootNode(o confrontandolo con P.NextSibling).
Forse cambiando il file
while Assigned(P) do
condizione a
while P <> tree.RootNode do
ti andrebbe bene
Quindi, grazie alla risposta di Tom e alla documentazione del componente, il genitore di RootNode è effettivamente l'albero stesso. Quindi, quando si effettua il ciclo con il Parent = nilpunto di arresto as, quando si raggiunge la radice del genitore (albero), viene eseguito il codice Includein GetNodeDatae, nel mio caso, quando si accede alla Stateproprietà, il suo indirizzo era molto vicino all'indirizzo Nome dell'albero , perché l'indirizzo del "nodo" era l'albero.
Quindi, posso risolvere il loop per verificare Node <> Tree.RootNodee anche fissare la GetNodeDataper controllare anche se il nodo è Self: or (Node = Pointer(Self), nel caso in cui un altro codice nella mia applicazione (eredità di 2 milioni di codice +) utilizza lo stesso ciclo sbagliato :
Prima:
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;
Dopo:
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;