Parent of RootNode of TVirtualStringTree
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
Đầ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
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;