Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

VirtualStringTree - Pointer stored <> Pointer retrieved ! Is my coding approach wrong?

I'm using a TVirtualStringTree to store pointers to records.

Originally there is a TList that contains the list of records.

I'm using the OnInitNode event to iterate through the TList and assign each records' data to the tree's nodes.

However, when retrieving the data associated with a node in the OnNewText event handler, the pointer returned has a different address than the one originally stored in the tree.

Further, I can see through debugging that the pointer (to the record data) retrieved from the node is not the same as the one originally stored in the node. I need to save changed data to a database and need to reference the record with the changed data. It should be simple as referencing the pointer, but the problem is that the pointer is not the same.

I'm not sure what I'm doing wrong and hope someone can help me fix this.

Thanks in advance.

Here is my code:

Data structure and declarations:

  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;

Loading the data into the TList, and then into the tree:

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;

Retrieving the data. I noticed the problem here:

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;

This is where I save the list data to database, and the reason that I need the tree to change the original data and not a copy:

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;

Here's a screenshot of the from the Debug->Watch List window:

enter image description here

like image 875
Steve F Avatar asked Nov 06 '14 16:11

Steve F


1 Answers

To answer to the question in the title, I would say yes, your coding approach is wrong.

The mistake is that you don't save a pointer to your data record to the VT's node, you're allocating whole (separate) TTherapData record for each node! So the "mistake" is the line

vstRxList_Asg.NodeDataSize := SizeOf(TTherapData);

in the TfmPatient_Conslt.LoadTherapList method. What you probably want is an additional record which holds pointer to the data record:

type
  TVTNodeData = record
    TherapData: PTherapData;
  end;
  PVTNodeData = ^TVTNodeData;

and use that record as node data record:

vstRxList_Asg.NodeDataSize := SizeOf(TVTNodeData);

and node init becomes something like

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;

and using the data in other tree events like

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;
like image 104
ain Avatar answered Nov 18 '22 02:11

ain