Padre de RootNode de TVirtualStringTree

Sep 18 2020

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

2 TomBrunberg Sep 18 2020 at 14:19

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

1 RodrigoCaetano Sep 18 2020 at 19:57

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;