Jump to content
PawelPepe

How to free TListView Objects (String)

Recommended Posts

Hi,

I got a problem with TListView and Data. It seems that I got memory leaks, because Data pointers are not released (if I understand it well).

Let me show you what I do:

 

Here is a simple TListView (LV_Data). I add few items to the list (Report Style) into 3 Columns.

 

var
   i        : Integer;
   ListItem : TListItem;
   PObject  : PChar;

   for i := 0 to 3 do
      begin
         // Caption
         ListItem := AList.Items.Add;
         ListItem.Caption := 'My Caption' + '_' + Inttostr(i+1);

         // Language Name
         ListItem.SubItems.Add('Language' + '_' + Inttostr(i+1));
         // User Name
         ListItem.SubItems.Add('User Name' + '_' + Inttostr(i+1));

         // User Data Path
         PObject := StrAlloc(250);
         StrPCopy(PObject, 'PATH_TO_USER_DIRECTORY' + '_' + Inttostr(i+1));
         ListItem.Data := PObject;
      end;

 

As you can see, I also add "hidden" data (in this example a path to directory, as string). I can use it in program, without displaying it to user in listview.

I can read it in simple way (for examle, in ListView OnClick event:)

var
	sPath : string;

   if (LV_Data.Items.Count > 0) AND (LV_Data.ItemIndex <> -1) then
      begin
         sPath := StrPas(PChar(LV_Data.Items[LV_Data.ItemIndex].Data));
      end;

 

Everything works very nice... but, when I close program, I see there are some memory leaks.

 

---------------------------
Unexpected Memory Leak
---------------------------
An unexpected memory leak has occurred. The unexpected small block leaks are:

473 - 520 bytes: Unknown x 3

---------------------------
OK   
---------------------------
 

 

As far as I know, I have to free those List View Data bymyself. How to do this?

 

procedure TMainFrm.LV_DataDeletion(Sender: TObject; Item: TListItem);
begin
   if Item.Data <> nil then
      begin
         FreeMem(Item.Data);
         Item.Data := nil;
      end;
end;

Above code not working (why!?), got Invalid Pointer Operation.

 

Could you please tell me how to free this data to avoid memory leak?

 

Thank you in advance,

-Pawel

 

Edited by PawelPepe

Share this post


Link to post

As you already know, you need to use the TListView.OnDeletion event to dispose of the TListItem.Data memory that you are allocating.  If you read the documentation for StrAlloc(), the correct way to free that memory is with StrDispose(), not FreeMem():

procedure TMainFrm.LV_DataDeletion(Sender: TObject; Item: TListItem);
begin
  if Item.Data <> nil then
  begin
    StrDispose(PChar(Item.Data));
    Item.Data := nil;
  end;
end;

Alternatively, a slightly easier way is to use a normal String and let the compiler manage it. A String is implemented as a pointer, and you can store that pointer in the TListItem.Data. You would just have to ensure its reference count is incremented while stored in TListItem.Data, and decremented when you are done using it:

var
  i        : Integer;
  ListItem : TListItem;
  PObject  : Pointer;
begin
  for i := 0 to 3 do
  begin
    ListItem := AList.Items.Add;
    ...
    PObject := nil;
    String(PObject) := '...';
    ListItem.Data := PObject;
  end;
procedure TMainFrm.LV_DataDeletion(Sender: TObject; Item: TListItem);
var
  PObject: Pointer:
begin
  if Item.Data <> nil then
  begin
    PObject := Item.Data;
    Item.Data := nil;
    String(PObject) := '';
  end;
end;
var
  sPath : string;
begin
  if (LV_Data.Items.Count > 0) AND (LV_Data.ItemIndex <> -1) then
  begin
    sPath := String(LV_Data.Items[LV_Data.ItemIndex].Data);
  end;

That being said, a much safer and cleaner solution is to not use the TListItem.Data property at all.  Instead, derive a new class from TListItem and add a String member to it, then use the TListView.OnCreateItemClass event to let TListView create instances of your derived class:

type
  TMyListItem = class(TListItem)
  public
    Path: string;
  end;

procedure TMainFrm.LV_DataCreateItemClass(Sender: TCustomListView;
  var ItemClass: TListItemClass);
begin
  ItemClass := TMyListItem;
end;
var
  i        : Integer;
  ListItem : TListItem;
begin
  for i := 0 to 3 do
  begin
    ListItem := AList.Items.Add;
    ...
    TMyListItem(ListItem).Path := '...';
  end;
var
  sPath : string;
begin
  if (LV_Data.Items.Count > 0) AND (LV_Data.ItemIndex <> -1) then
  begin
    sPath := TMyListItem(LV_Data.Items[LV_Data.ItemIndex]).Path;
  end;

No mess, no fuss.  The memory is managed for you, and will be cleaned up automatically, no OnDeletion handler needed.

Edited by Remy Lebeau
  • Like 1

Share this post


Link to post

Remy Lebeau

 

Thank you!!!

It is working (first method)

I will also try the second one.

 

Thank you for very fast response and your knowledge.

Best regards,

-Pawel

 

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×