Parent of RootNode of TVirtualStringTree

Sep 18 2020

Theo tài liệu, thuộc tính RootNode của TVirtualStringTree là một nút ẩn, cha của tất cả các nút do người dùng tạo. Nhưng Parent của RootNode đã được thiết lập. Tôi đang đối mặt với vấn đề sau:

Đôi khi, khi đóng một biểu mẫu có TVirtualStringTree trong đó, tôi gặp lỗi vi phạm quyền truy cập. Gỡ lỗi, lỗi xảy ra khi tryng truy cập thuộc tính Tên của thành phần (virtualstringtree). Nếu tôi cố gắng đánh giá thuộc tính Tên, tôi nhận được "Giá trị không thể truy cập". Tìm hiểu vấn đề, tôi phát hiện ra rằng không thể thay đổi thuộc tính Name của một thành phần trong thời gian chạy :

Cảnh báo: Thay đổi Tên trong thời gian chạy khiến mọi tham chiếu đến tên cũ trở nên không xác định. Bất kỳ mã tiếp theo nào sử dụng tên cũ sẽ gây ra một ngoại lệ.

Tình cờ là tôi không thay đổi điều này trong mã của mình. Sử dụng một điểm ngắt dữ liệu trong thuộc tính Name, tôi thấy rằng nó đang thay đổi trong vòng lặp virtualstringtree, giống như thế này (mã ví dụ mà tôi đang sử dụng trong một ứng dụng mẫu để kiểm tra sự cố, nhưng mã sản xuất cũng tương tự):

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

GetNodeData như sau:

 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;

Cho rằng tôi có cái cây sau:

Node 1
  Node 2
    Node 3

Khi nút 3 được chọn, vòng lặp được thực hiện 5 lần. 3 trong các nút đã tạo của tôi, sau đó là RootNode (ẩn), và sau đó là Parent của root. Chỉ có nút gốc của nút gốc cha là nil. Trong phương thức GetNodeData được xác minh nếu nó là nút gốc, trả về nil. Nhưng đối với mã gốc này, vì nó không phải là mã gốc, hãy nhập mã khác. Bây giờ là vấn đề:

Địa chỉ của thuộc tính Name của virtualstrintree là $ 141B9768

Địa chỉ của thuộc tính States của nút gốc là $ 141B976A

Khi đặt điểm ngắt dữ liệu trong thuộc tính Name, nó sẽ được thay đổi tại điểm này:

Include(Node.States, vsOnFreeNodeCallRequired);

và tạo AV.

Tôi biết tôi có thể thay đổi vòng lặp, để kiểm tra nút gốc một cách chính xác , nhưng tôi muốn hiểu điều này, vì vậy tôi có thể sửa thành phần (nếu đó là lỗi thành phần), thay vì mã của tôi.

Mã:

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;

Chỉ cần đặt một cái cây và một nút trong biểu mẫu, tôi đặt vòng lặp khi nhấp vào nút.

Trả lời

2 TomBrunberg Sep 18 2020 at 14:19

Đầu tiên, tài liệu: (ví dụ) https://documentation.help/VirtualTreeview/TBaseVirtualTree_RootNode.html

nói (nhấn mạnh của tôi)

thuộc tính RootNode: PVirtualNode;

Sự miêu tả

Để neo cấu trúc phân cấp cây, một nút cây bên trong được duy trì, hầu hết giống như bất kỳ nút cây nào khác nhưng đôi khi có cách xử lý khác nhau. Nút gốc luôn được mở rộng và khởi tạo. Thành viên cha của nó trỏ đến chế độ xem dạng cây mà nút thuộc về và các thành viên PreviousSibling và NextSibling của nó trỏ đến chính nút gốc để có thể thực sự nhận ra nút này .

Ghi chú

Bạn không nên sử dụng nút gốc để lặp qua cây. Nó chỉ có thể truy cập công khai vì nó là nút cha của tất cả các nút cấp cao nhất và có thể được sử dụng để kiểm tra một nút xem nó có phải là nút cấp cao nhất hay không.

Thứ hai

Trong mã của bạn

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

bạn đang đi ngang qua nút gốc bên trong một cách vô điều kiện vì bạn không kiểm tra xem có phải Plà nút gốc bên trong hay không, bằng cách so sánh với tree.RootNode(hoặc so sánh với P.NextSibling).

Có lẽ thay đổi

while Assigned(P) do

điều kiện để

while P <> tree.RootNode do

sẽ phù hợp với bạn

1 RodrigoCaetano Sep 18 2020 at 19:57

Vì vậy, nhờ câu trả lời của Tom và tài liệu về thành phần, cha mẹ của RootNode thực chất là chính cây. Vì vậy, khi thực hiện vòng lặp với Parent = nilđiểm dừng as, khi đến gốc gốc (cây), mã Includetrong GetNodeDatađược thực thi và, trong trường hợp của tôi, khi truy cập thuộc Statetính, địa chỉ của nó rất gần với địa chỉ Tên của cây , bởi vì địa chỉ của "nút" là cây.

Vì vậy, tôi sửa chữa các vòng lặp để kiểm tra Node <> Tree.RootNodevà cũng có thể sửa chữa GetNodeDatacũng để kiểm tra xem Node là tự phục vụ: or (Node = Pointer(Self), chỉ trong trường hợp các mã khác trong ứng dụng của tôi (di sản của 2 triệu + code) sử dụng vòng lặp sai tương tự :

Trước:

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;

Sau:

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;