Pai de RootNode de TVirtualStringTree
De acordo com a documentação, a propriedade RootNode de TVirtualStringTree é um nó oculto, pai de todos os nós criados pelo usuário. Mas o pai do RootNode está definido. Estou enfrentando o seguinte problema:
Às vezes, ao fechar um formulário que contém uma TVirtualStringTree, recebo um erro de violação de acesso. Depurando, o erro ocorre ao tentar acessar a propriedade Name do componente (virtualstringtree). Se tento avaliar a propriedade Name, obtenho "Valor inacessível". Explorando o problema, descobri que a propriedade Name de um componente não pode ser alterada no tempo de execução :
Aviso: alterar o nome em tempo de execução faz com que todas as referências ao nome antigo se tornem indefinidas. Qualquer código subsequente que use o nome antigo causará uma exceção.
Acontece que não estou mudando isso no meu código. Usando um ponto de interrupção de dados na propriedade Name, vi que ele estava mudando em um loop virtualstringtree, algo como este (código de exemplo que estou usando em um aplicativo de amostra para testar o problema, mas o código de produção é semelhante):
var
N: PNode;
P: PVirtualNode;
begin
P := tree.FocusedNode;
while Assigned(P) do
begin
N := tree.GetNodeData(P);
P := P.Parent;
end;
O GetNodeData é o seguinte:
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 que tenho a seguinte árvore:
Node 1
Node 2
Node 3
Quando o Nó 3 é selecionado, o loop é executado 5 vezes. 3 em meus nós criados, em seguida, o RootNode (oculto) e, em seguida, o pai da raiz. Apenas o pai do nó raiz pai é nulo. No método GetNodeData é verificado se é o nó raiz, retorna nulo. Mas para este pai da raiz, uma vez que não é a raiz, insira o código else. Agora, aqui está o problema:
O endereço da propriedade Name da virtualstrintree é $ 141B9768
O endereço da propriedade dos Estados do pai do nó raiz é $ 141B976A
Ao colocar o ponto de interrupção de dados na propriedade Name, ele é alterado neste ponto:
Include(Node.States, vsOnFreeNodeCallRequired);
e gerando o AV.
Sei que posso mudar o loop, para verificar o nó raiz corretamente , mas quero entender isso, então talvez conserte o componente (se for um bug de componente), em vez do meu código.
Código:
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 colocar uma árvore e um botão no formulário, coloco o loop no clique do botão.
Respostas
Primeiro, a documentação: (por exemplo) https://documentation.help/VirtualTreeview/TBaseVirtualTree_RootNode.html
diz (ênfase minha)
propriedade RootNode: PVirtualNode;
Descrição
Para ancorar a hierarquia da árvore, um nó interno da árvore é mantido, o qual é praticamente igual a qualquer outro nó da árvore, mas às vezes tem um tratamento diferente. O nó raiz é sempre expandido e inicializado. Seu membro pai aponta para a visualização em árvore à qual o nó pertence e seus membros PreviousSibling e NextSibling apontam para o próprio nó raiz para possibilitar o reconhecimento real desse nó .
Notas
Você não deve usar o nó raiz para iterar na árvore. Ele só é acessível publicamente porque é o pai de todos os nós de nível superior e pode ser usado para testar um nó, seja ele um nó de nível superior ou não.
Em segundo lugar
Em seu código
var
N: PNode;
P: PVirtualNode;
begin
P := tree.FocusedNode;
while Assigned(P) do
begin
N := tree.GetNodeData(P);
P := P.Parent;
end;
você está cruzando incondicionalmente para cima, além do nó raiz interno porque não está verificando se Pé a raiz interna, comparando com tree.RootNode(ou comparando com P.NextSibling).
Talvez mudando o
while Assigned(P) do
condição para
while P <> tree.RootNode do
serviria para você
Portanto, graças à resposta de Tom e à documentação do componente, o pai de RootNode é na verdade a própria árvore. Assim, ao fazer o loop com o Parent = nilponto de parada as, ao atingir a raiz do pai (árvore), o código Includeem GetNodeDataé executado, e, no meu caso, ao acessar a Statepropriedade, seu endereço estava muito próximo do endereço do nome da árvore , porque o endereço do "nó" era a árvore.
Então, eu corrigi o loop para verificar Node <> Tree.RootNodee também corrigir o GetNodeDatatambém verificar se o nó é manobrista: or (Node = Pointer(Self), apenas no caso de outro código na minha aplicação (legado de 2 milhões + código) usa o mesmo circuito errado :
Antes:
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;
Após:
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;