ผู้ปกครองของ 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 ในโหนดที่ฉันสร้างขึ้นจากนั้น RootNode (ซ่อนอยู่) และจากนั้นเป็นพาเรนต์ของรูท พาเรนต์ของโหนดรูทพาเรนต์เท่านั้นที่เป็นศูนย์ ในเมธอด GetNodeData ได้รับการตรวจสอบว่าเป็นโหนดรูทหรือไม่ให้ส่งคืนค่า nil แต่สำหรับพาเรนต์ของรูทนี้เนื่องจากไม่ใช่รูทให้ป้อนโค้ดอื่น นี่คือปัญหา:

ที่อยู่ของคุณสมบัติ Name ของ virtualstrintree คือ $ 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;

คำอธิบาย

สำหรับการยึดลำดับชั้นของต้นไม้จะมีการบำรุงรักษาโหนดต้นไม้ภายในซึ่งส่วนใหญ่จะเหมือนกับโหนดต้นไม้อื่น ๆ แต่บางครั้งก็มีการจัดการที่แตกต่างกัน โหนดรูทจะถูกขยายและเริ่มต้นเสมอ จุดที่สมาชิกผู้ปกครองในการ TreeView ที่โหนดเป็นสมาชิกและ 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

ดังนั้นต้องขอบคุณคำตอบของ Tom และเอกสารของส่วนประกอบพาเรนต์ของ RootNode จึงเป็นต้นไม้นั้นเอง ดังนั้นเมื่อสร้างลูปด้วยParent = nilas point of stop เมื่อไปถึงรูทของพาเรนต์ (ทรี) โค้ดIncludeในGetNodeDataจะถูกเรียกใช้งานและในกรณีของฉันเมื่อเข้าถึงStateคุณสมบัติแอดเดรสของมันอยู่ใกล้กับที่อยู่ชื่อของทรีมาก เนื่องจากที่อยู่ของ "โหนด" คือต้นไม้

ดังนั้นฉันจึงแก้ไขลูปเพื่อตรวจสอบNode <> Tree.RootNodeและแก้ไขGetNodeDataเพื่อตรวจสอบด้วยว่าโหนดเป็นตัวเอง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;