sddk
sddk

Reputation: 1115

populating TListView by using temp TListItems

I want to refresh the list of listview in background, to do that i created a temporary TListItems, but i can not assign it to my listview. If i create TListItems and TListItem an access violation error occurs;

 var
  lis:TListItems;
  li:TListItem;
begin
  lis := TListItems.Create(nil);
  try
    li := TListItem.Create(nil);
    li.Caption := 'test'; // at this line av occurs
    lis.AddItem(li);
    ListView1.Items.BeginUpdate;
    try
      ListView1.Items.Assign(lis);
    finally
      ListView1.Items.EndUpdate;
    end;
  finally
    lis.Destroy();
  end;

If i use ListView1 as Owner while creating TListItems new line does not appear;

 var
  lis:TListItems;
  li:TListItem;
begin
  lis := TListItems.Create(ListView1);
  try
    li := TListItem.Create(lis);
    li.Caption := 'test';
    lis.AddItem(li);
    ListView1.Items.BeginUpdate;
    try
      ListView1.Items.Assign(lis);
    finally
      ListView1.Items.EndUpdate;
    end;
  finally
    lis.Destroy();
  end;

So i'want to prepare a new list in background and assign it to listview, how can i do it?

Note: preparing the list takes long time, this is why i'm preparing it in background. (i'm filling the list by a thread and protect it by using TRTLCriticalSection)

Upvotes: 1

Views: 1183

Answers (1)

Remy Lebeau
Remy Lebeau

Reputation: 595367

TListItems and TListItem are very intimately tied to TListView and thus cannot be used in a standalone manner like you are attempting to do. TListItems and TListItem both delegate to TListView to handle their work.

You get an AV in your first example because there is no TListView assigned to handle the work.

In your second example, the new list item does not appear correctly because it has not been added to the ListView yet when you assign its Caption, so there is nothing for the property setter to update. And AddItem() doesn't apply pre-existing property values to a newly inserted list item. The individual property setters must be used for that instead.

You must use the TListItems.Add() method FIRST instead of calling AddItem() directly, and THEN you can modify the new TListItem as needed, eg:

var
  NewList: TStringList;
  Lock: TCriticalSection;

...

// in a worker thread...

Lock.Enter;
try
  NewList.Clear;
  NewList.Add('test');
  ...
finally
  Lock.Leave;
end;
// signal main UI thread that a new list is ready ...

...

// in the main UI thread when the signal is received...

var
  li: TListItem;
  i: Integer;
begin
  Lock.Enter;
  try
    ListView1.Items.BeginUpdate;
    try
      ListView1.Items.Clear;
      for i := 0 to NewList.Count-1 do
      begin
        li := ListView1.Items.Add;
        li.Caption := NewList[i];
        ...
      end;
    finally
      ListView1.Items.EndUpdate;
    end;
  finally
    Lock.Leave;
  end;
end;

But, as David H said in comments, using the ListView in virtual mode (set the OwnerData property to true and use the TListView.OnData... events) is a better way to handle this situation:

var
  NewList: TStringList;
  Lock: TCriticalSection;

...

// in a worker thread...

Lock.Enter;
try
  NewList.Clear;
  NewList.Add('test');
  ...
finally
  Lock.Leave;
end;
// signal main UI thread that a new list is ready ...

...

// in the main UI thread...

private
  MyListItems: TStringList; // or whatever you want to use to store your item data

...

begin
  Lock.Enter;
  try
    MyListItems.Assign(NewList);
  finally
    Lock.Leave;
  end;
  ListView1.Items.Count := MyListItems.Count;
end;

procedure TMyForm.ListView1Data(Sender: TObject; Item: TListItem);
begin
  Item.Caption := MyListItems[Item.Index];
  ...
end;

Upvotes: 2

Related Questions