Родитель RootNode TVirtualStringTree

Sep 18 2020

Согласно документации, свойство RootNode объекта TVirtualStringTree является скрытым узлом, родительским для всех узлов, созданных пользователем. Но установлен родительский узел RootNode. Я столкнулся со следующей проблемой:

Иногда при закрытии формы, в которой есть TVirtualStringTree, я получаю ошибку нарушения прав доступа. Отладка, ошибка возникает при попытке доступа к свойству Name компонента (virtualstringtree). Если я пытаюсь оценить свойство Name, я получаю «Недоступное значение». Покопавшись в проблеме, я обнаружил, что свойство Name компонента нельзя изменить во время выполнения :

Предупреждение: изменение имени во время выполнения приводит к тому, что любые ссылки на старое имя становятся неопределенными. Любой последующий код, использующий старое имя, вызовет исключение.

Случилось так, что я не меняю это в своем коде. Используя точку останова по данным в свойстве Name, я увидел, что он меняется в цикле virtualstringtree, примерно так (пример кода, который я использую в примере приложения для проверки проблемы, но производственный код аналогичен):

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

GetNodeData выглядит следующим образом:

 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;

При этом у меня есть следующее дерево:

Node 1
  Node 2
    Node 3

Когда выбран Узел 3, цикл выполняется 5 раз. 3 в моих созданных узлах, затем корневой узел (скрытый), а затем родительский узел корня. Только родительский элемент родительского корневого узла равен нулю. В методе GetNodeData проверяется, является ли он корневым узлом, возвращает ноль. Но этот родительский элемент корня, поскольку он не является корнем, входит в код else. Теперь вот в чем проблема:

Адрес свойства Name виртуального дерева: $ 141B9768.

Адрес свойства States родительского узла корневого узла: $ 141B976A.

Когда точка останова по данным помещается в свойство Name, на этом этапе она изменяется:

Include(Node.States, vsOnFreeNodeCallRequired);

и создание AV.

Я знаю , что может изменить цикл, чтобы проверить корневой узел правильно , но я хочу , чтобы это понять, так что я , возможно , исправить компонент (если он является компонентом ошибки), вместо того , чтобы мой код.

Код:

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;

Просто поместите дерево и кнопку в форму, я помещаю петлю в щелчок по кнопке.

Ответы

2 TomBrunberg Sep 18 2020 at 14:19

Во-первых, документация: (например) https://documentation.help/VirtualTreeview/TBaseVirtualTree_RootNode.html

говорит (курсив мой)

свойство RootNode: PVirtualNode;

Описание

Для привязки иерархии дерева поддерживается внутренний узел дерева, который в основном такой же, как и любой другой узел дерева, но иногда обрабатывается иначе. Корневой узел всегда расширяется и инициализируется. Его родительский член указывает на древовидное представление, которому принадлежит узел, а его члены PreviousSibling и NextSibling указывают на сам корневой узел, чтобы можно было фактически распознать этот узел .

Заметки

Вы не должны использовать корневой узел для итерации по дереву. Он общедоступен только потому, что является родительским для всех узлов верхнего уровня и может использоваться для проверки того, является ли он узлом верхнего уровня или нет.

во-вторых

В вашем коде

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

вы безоговорочно проходите вверх мимо внутреннего корневого узла, потому что вы не проверяете, Pявляется ли он внутренним корнем, сравнивая tree.RootNode(или сравнивая с P.NextSibling).

Возможно изменение

while Assigned(P) do

условие для

while P <> tree.RootNode do

подойдет вам

1 RodrigoCaetano Sep 18 2020 at 19:57

Итак, благодаря ответу Тома и документации компонента родительский узел RootNode фактически является самим деревом. Итак, при создании цикла с Parent = nilточкой остановки as при достижении родительского корня (дерева) выполняется код Includeв GetNodeData, и, в моем случае, при доступе к Stateсвойству его адрес был очень близок к адресу Name дерева , потому что адресом «узла» было дерево.

Итак, я исправляю цикл, чтобы проверить, Node <> Tree.RootNodeа также исправляю, GetNodeDataчтобы также проверить, является ли узел Self : or (Node = Pointer(Self), на всякий случай другой код в моем приложении (устаревший код 2 миллиона +) использует тот же неправильный цикл :

Перед:

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;

После:

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;