Padre de RootNode de TVirtualStringTree
Según la documentación, la propiedad RootNode de TVirtualStringTree es un nodo oculto, padre de todos los nodos creados por el usuario. Pero el padre del RootNode está establecido. Me enfrento al siguiente problema:
A veces, al cerrar un formulario que tiene un TVirtualStringTree, aparece un error de infracción de acceso. Depurando, el error ocurre cuando se intenta acceder a la propiedad Name del componente (virtualstringtree). Si intento evaluar la propiedad Name, obtengo "Valor inaccesible". Profundizando en el problema, descubrí que la propiedad Name de un componente no se puede cambiar en tiempo de ejecución :
Advertencia: Cambiar el nombre en tiempo de ejecución hace que cualquier referencia al nombre anterior quede indefinida. Cualquier código posterior que utilice el nombre anterior provocará una excepción.
Sucedió que no estoy cambiando esto en mi código. Usando un punto de interrupción de datos en la propiedad Name, vi que estaba cambiando en un bucle virtualstringtree, algo como esto (código de ejemplo que estoy usando en una aplicación de muestra para probar el problema, pero el código de producción es similar):
var
N: PNode;
P: PVirtualNode;
begin
P := tree.FocusedNode;
while Assigned(P) do
begin
N := tree.GetNodeData(P);
P := P.Parent;
end;
GetNodeData es el siguiente:
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 tengo el siguiente árbol:
Node 1
Node 2
Node 3
Cuando se selecciona el Nodo 3, el ciclo se ejecuta 5 veces. 3 en mis nodos creados, luego el RootNode (oculto) y luego el Padre de la raíz. Solo el padre del nodo raíz padre es nulo. En el método GetNodeData se verifica si es el nodo raíz, devuelve nil. Pero para este padre de la raíz, dado que no es la raíz, ingrese el código else. Ahora aquí está el problema:
La dirección de la propiedad Name del árbol virtual es $ 141B9768
La dirección de la propiedad States del padre del nodo raíz es $ 141B976A
Al poner el punto de interrupción de datos en la propiedad Name, se cambia en este punto:
Include(Node.States, vsOnFreeNodeCallRequired);
y generando el AV.
Sé que puedo cambiar el bucle para verificar el nodo raíz correctamente , pero quiero entender esto, así que tal vez arregle el componente (si es un error de componente), en lugar de mi 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;
Solo pon un árbol y un botón en el formulario, pongo el bucle al hacer clic en el botón.
Respuestas
Primero, la documentación: (p. Ej.) https://documentation.help/VirtualTreeview/TBaseVirtualTree_RootNode.html
dice (énfasis mío)
propiedad RootNode: PVirtualNode;
Descripción
Para anclar la jerarquía del árbol, se mantiene un nodo de árbol interno que en su mayoría es como cualquier otro nodo de árbol, pero a veces se maneja de manera diferente. El nodo raíz siempre se expande e inicializa. Su miembro padre apunta a la vista de árbol a la que pertenece el nodo y sus miembros PreviousSibling y NextSibling apuntan al nodo raíz mismo para hacer posible reconocer realmente este nodo .
Notas
No debe utilizar el nodo raíz para recorrer en iteración el árbol. Solo es de acceso público porque es el padre de todos los nodos de nivel superior y se puede utilizar para probar un nodo, ya sea que sea un nodo de nivel superior o no.
en segundo lugar
En tu código
var
N: PNode;
P: PVirtualNode;
begin
P := tree.FocusedNode;
while Assigned(P) do
begin
N := tree.GetNodeData(P);
P := P.Parent;
end;
está atravesando incondicionalmente hacia arriba más allá del nodo raíz interno porque no está comprobando que Psea la raíz interna, comparando con tree.RootNode(o comparando con P.NextSibling).
Quizás cambiando el
while Assigned(P) do
condición para
while P <> tree.RootNode do
te vendría bien
Entonces, gracias a la respuesta de Tom y la documentación del componente, el padre de RootNode es en realidad el árbol en sí. Entonces, al hacer el bucle con el Parent = nilpunto de parada como, al llegar a la raíz (árbol) del padre, se ejecuta el código Includeen GetNodeData, y, en mi caso, al acceder a la Statepropiedad, su dirección estaba muy cerca de la dirección del nombre del árbol. , porque la dirección de "nodo" era el árbol.
Por lo tanto, puedo solucionar el bucle para comprobar Node <> Tree.RootNodey reparar el GetNodeDatatambién comprobar si el Nodo es sin asistencia: or (Node = Pointer(Self), por si acaso otro código en mi aplicación (legado de 2 millones + código) utiliza el mismo bucle equivocada :
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;
Despué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;