VirtualStringTree - 存储的指针 <> 检索到的指针!我的编码方法有问题吗?

5
我正在使用TVirtualStringTree来存储记录的指针。
最初有一个TList包含记录列表。
我使用OnInitNode事件遍历TList并将每个记录的数据分配给树节点。
然而,在OnNewText事件处理程序中检索与节点关联的数据时,返回的指针具有与最初存储在树中的指针不同的地址。
此外,通过调试可以看到从节点检索到的指针(记录数据)与最初存储在节点中的指针不同。我需要将更改后的数据保存到数据库并需要引用带有更改后的数据的记录。它应该简单地引用指针,但问题是指针不同。
我不确定自己做错了什么,希望有人能帮我解决这个问题。
先行致谢。
以下是我的代码:
数据结构和声明:
  TTherapData = record
    TherapID: Integer;
    TherapName: String[120];
    TherapInstr: String[120];
    Selected_DB: Byte;
    Selected: Byte;
  end;

  PTherapData = ^TTherapData;

  FTherapDataList: TList<PTherapData>;

  FTherapDataListAsg_Iter: Integer;

  vstRxList_Asg: TVirtualStringTree;

将数据加载到TList中,然后加载到树中:

procedure TfmPatient_Conslt.LoadTherapList(const ADBLoad: Boolean = False);
var
  TherapData: PTherapData;
  d, x: Integer;
begin
    datamod.uspLKTHERAP_S.First;
    while not datamod.uspLKTHERAP_S.Eof do
    begin
      New(TherapData);

      TherapData^.TherapID := datamod.uspLKTHERAP_SROW_ID.AsInteger;
      TherapData^.TherapName := datamod.uspLKTHERAP_SIMPRTHERAP.AsString;
      TherapData^.TherapInstr := EmptyStr;
      TherapData^.Selected := 0;
      TherapData^.Selected_DB := 0;

      FTherapDataList.Add(TherapData);

      datamod.uspLKTHERAP_S.Next;
    end;


    datamod.uspCONSLT_RX_S.First;
    while not datamod.uspCONSLT_RX_S.Eof do
    begin
      d := datamod.uspCONSLT_RX_SRX_ID.AsInteger;
      TherapData := FTherapDataList[TherapDataList_GetIndexOfID(d)];
      TherapData^.TherapInstr := datamod.uspCONSLT_RX_SRX_INSTRUCTION.AsString;
      TherapData^.Selected := 1;
      TherapData^.Selected_DB := 1;

      datamod.uspCONSLT_RX_S.Next;
    end;

  x := TherapDataList_CountSelectedItems;

  FTherapDataListAsg_Iter := 0;
  vstRxList_Asg.NodeDataSize := SizeOf(TTherapData);
  vstRxList_Asg.RootNodeCount := 0;
  vstRxList_Asg.RootNodeCount := x;
end;

procedure TfmPatient_Conslt.vstRxList_AsgInitNode(Sender: TBaseVirtualTree; ParentNode, Node: PVirtualNode;
  var InitialStates: TVirtualNodeInitStates);
var
  TherapData: PTherapData;
begin
  TherapData := Sender.GetNodeData(Node);

  while (FTherapDataList[FTherapDataListAsg_Iter]^.Selected <> 1) do
    Inc(FTherapDataListAsg_Iter);

  TherapData^.TherapID := FTherapDataList[FTherapDataListAsg_Iter]^.TherapID;
  TherapData^.TherapName := FTherapDataList[FTherapDataListAsg_Iter]^.TherapName;
  TherapData^.TherapInstr := FTherapDataList[FTherapDataListAsg_Iter]^.TherapInstr;

  { TherapData := FTherapDataList[FTherapDataListAsg_Iter]; } //  
  { TherapData^ := FTherapDataList[FTherapDataListAsg_Iter]^; } //  

  Inc(FTherapDataListAsg_Iter);
end;

procedure TfmPatient_Conslt.vstRxList_AsgGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex;
  TextType: TVSTTextType; var CellText: string);
var
  TherapData: PTherapData;
begin
  TherapData := Sender.GetNodeData(Node);
  if Assigned(TherapData) then
    if (Column = 0) then
      CellText := TherapData^.TherapName
    else if (Column = 1) then
      CellText := TherapData^.TherapInstr;
end;

procedure TfmPatient_Conslt.vstRxList_AsgEditing(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex;
  var Allowed: Boolean);
begin
  Allowed := (Column = 1);
end;

获取数据。我发现问题在这里:

procedure TfmPatient_Conslt.vstRxList_AsgNewText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex;
  NewText: string);
var
  TherapData: PTherapData;
begin

  if (Column = 1) then
  begin
    TherapData := Sender.GetNodeData(Node);
    if Assigned(TherapData) then                 // <---- There is a debug breakpoint here 
                                                 // and the watch window screen-shot 
                                                 // is taken here
      TherapData^.TherapInstr := NewText;

    // Showmessage(Format('%p', [TherapData]));  // <---- The pointer value is not the same
                                                 //       as that originally stored !

  end;

end;

这里是我将列表数据保存到数据库的位置,我需要树来更改原始数据而不是复制品的原因是:

procedure TfmPatient_Conslt.SaveRxListToDB;
var
  TherapData: PTherapData;
begin

  for TherapData in FTherapDataList do
  begin
    if (TherapData^.Selected = 1) and (TherapData^.Selected_DB = 0) then
    begin
      // Add new entries to DB
      // :ROW_ID, :CONSLT_ID, :RX_ID, :RX_INSTRUCTION
      datamod.uspCONSLT_RX_I.ParamByName('ROW_ID').AsInteger := 0;
      datamod.uspCONSLT_RX_I.ParamByName('CONSLT_ID').AsInteger := FConsultationID;
      datamod.uspCONSLT_RX_I.ParamByName('RX_ID').AsInteger := TherapData^.TherapID;
      datamod.uspCONSLT_RX_I.ParamByName('RX_INSTRUCTION').AsString := TherapData^.TherapInstr;
      datamod.uspCONSLT_RX_I.PrepareSQL(False);
      datamod.uspCONSLT_RX_I.ExecProc;

      TherapData^.Selected_DB := 1;
    end
    else if (TherapData^.Selected = 1) and (TherapData^.Selected_DB = 1) then
    begin
      // Update existing DB entries
      // :CONSLT_ID, :RX_ID, :RX_INSTRUCTION
      datamod.uspCONSLT_RX_U.ParamByName('CONSLT_ID').AsInteger := FConsultationID;
      datamod.uspCONSLT_RX_U.ParamByName('RX_ID').AsInteger := TherapData^.TherapID;
      datamod.uspCONSLT_RX_U.ParamByName('RX_INSTRUCTION').AsString := TherapData^.TherapInstr;
      datamod.uspCONSLT_RX_U.PrepareSQL(False);
      datamod.uspCONSLT_RX_U.ExecProc;
    end
    else if (TherapData^.Selected = 0) and (TherapData^.Selected_DB = 1) then
    begin
      // Delete removed entries from DB
      // :CONSLT_ID, :RX_ID
      datamod.uspCONSLT_RX_D.ParamByName('CONSLT_ID').AsInteger := FConsultationID;
      datamod.uspCONSLT_RX_D.ParamByName('RX_ID').AsInteger := TherapData^.TherapID;
      datamod.uspCONSLT_RX_D.PrepareSQL(False);
      datamod.uspCONSLT_RX_D.ExecProc;

      TherapData^.Selected_DB := 0;
    end;
  end;

end;

这是从“调试->监视列表”窗口中截取的屏幕截图:

enter image description here


1
哦,终于我们得到了c :) - TLama
@TLama :) 是的。这是一段漫长而奇怪的旅程。 - Steve F
1个回答

5
回答标题中的问题,我会说是的,你的编码方法是错误的。
错误在于你没有将指向数据记录的指针保存到VT节点中,而是为每个节点分配了完整(独立的)TTherapData记录!因此,“错误”出现在以下这行代码上。
vstRxList_Asg.NodeDataSize := SizeOf(TTherapData);

TfmPatient_Conslt.LoadTherapList方法中,您可能需要一个额外的记录来保存指向数据记录的指针:
type
  TVTNodeData = record
    TherapData: PTherapData;
  end;
  PVTNodeData = ^TVTNodeData;

并将该记录用作节点数据记录:

vstRxList_Asg.NodeDataSize := SizeOf(TVTNodeData);

节点初始化变成了以下类似形式

procedure TfmPatient_Conslt.vstRxList_AsgInitNode(Sender: TBaseVirtualTree; ParentNode, Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates);
var
  NodeData: PVTNodeData;
begin
  NodeData := Sender.GetNodeData(Node);

  while (FTherapDataList[FTherapDataListAsg_Iter]^.Selected <> 1) do
    Inc(FTherapDataListAsg_Iter);

  NodeData^.TherapData := FTherapDataList[FTherapDataListAsg_Iter];

  Inc(FTherapDataListAsg_Iter);
end;

并且可以在其他树事件中使用数据,例如:

procedure TfmPatient_Conslt.vstRxList_AsgGetText(Sender: TBaseVirtualTree; Node:PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
var
  NodeData: PVTNodeData;
  TherapData: PTherapData;
begin
  NodeData := Sender.GetNodeData(Node);
  if Assigned(NodeData) and Assigned(NodeData.TherapData) then begin
    TherapData := NodeData.TherapData;
    if (Column = 0) then
      CellText := TherapData^.TherapName
    else if (Column = 1) then
      CellText := TherapData^.TherapInstr;
  end;
end;

1
谢谢。这真的让我受益匪浅。 - Steve F
在OnFreeNode事件处理程序中,我需要释放为节点(NodeData)分配的内存吗?如果是这样,应该如何释放? - Steve F
1
不,VT管理其数据记录的内存。但是,VT不会完成用户数据记录,因此,如果您的节点数据记录(TVTNodeData)包含像字符串或接口这样的托管类型,则需要完成这些操作,即将字符串字段设置为''(空字符串),接口设置为nil等。但在您的情况下,只有一个指针字段不需要任何完成操作(假设您在释放FTherapDataList时释放了分配给PTherapData记录的内存)。 - ain
再次感谢。当释放FTherapDataList时,我会释放PTherapData记录。 - Steve F

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接