Welcome, Guest
Guest Settings
Help

Thread: Load item image of TListView in Thread



Permlink Replies: 4 - Last Post: Oct 12, 2015 7:54 AM Last Post By: Lucio Fiore
Lucio Fiore

Posts: 7
Registered: 2/4/14
Load item image of TListView in Thread
Click to report abuse...   Click to reply to this thread Reply
  Posted: Oct 8, 2015 3:56 AM
Hi,

I have an app that show products with image in a TListView component.
I need to load image in TListView asynchronous so the app don't freeze until all'images are loaded.

I have used Anonymous Thread, but don't works, the listview.bitmap don't show the image
this is the code:

procedure TfrmMain.LoadImage(AListView: TListView);
begin
TThread.CreateAnonymousThread(
procedure
var
i: Integer;
sUrl : string;
myMemoryStream : TMemoryStream;
Image: TImage;
begin
try
try
AListView.BeginUpdate;
for i := 0 to AListView.Items.Count -1 do
begin
Image := TImage.Create(nil);
sUrl := AListView.Items[i].Data[TMultiDetailAppearanceNames.Detail3].ToString; // then url of image to load
sUrl := trim(sUrl);
sUrl := stringreplace(sUrl,'.tif','.jpg',[rfreplaceall]);
sUrl := TIdUri.urlEncode(sUrl);
if pos('.jpg', sUrl) > 0 then
begin
try
Image.Bitmap.LoadFromStream(LoadStreamFromUrl(sUrl));
except
end;

TThread.Synchronize(TThread.CurrentThread,
procedure ()
begin
AListView.Items[i].Bitmap:= Image.Bitmap;
Image.Free;
Image := nil;
end);
end;
end;
finally
AListView.EndUpdate;
end;
except on e:exception do
end;
end).start;
end;

Thanks

Remy Lebeau (Te...


Posts: 7,318
Registered: 12/23/01
Re: Load item image of TListView in Thread
Click to report abuse...   Click to reply to this thread Reply
  Posted: Oct 8, 2015 10:16 AM   in response to: Lucio Fiore in response to: Lucio Fiore
Lucio wrote:

I have used Anonymous Thread, but don't works, the listview.bitmap
don't show the image this is the code:

You are not synchronizing with the main UI thread when looping through the
ListView reading the URLs, or when creating the TImage objects (why are you
creating TImage objects at all? You are not using them visually. You should
be creating TBitmap objects instead). You must synchronize ALL UI operations,
eg:

procedure TfrmMain.LoadImage(AListView: TListView);
var
  ListItemCount: Integer;
begin
  ListItemCount := AListView.Items.Count;
  TThread.CreateAnonymousThread(
    procedure
    var
      i: Integer;
      sUrl : string;
      myMemoryStream : TMemoryStream;
      Bmp: TBitmap;
    begin
      for i := 0 to ListItemCount -1 do
      begin
        TThread.Synchronize(nil,
          procedure
          begin
            sUrl := AListView.Items[i].Data[TMultiDetailAppearanceNames.Detail3].ToString;
          end
        );
        // then url of image to load
        sUrl := Trim(sUrl);
        sUrl := StringReplace(sUrl, '.tif', '.jpg', [rfReplaceAll]);
        sUrl := TIdUri.urlEncode(sUrl);
        if pos('.jpg', sUrl) > 0 then
        begin
          Bmp := TBitmap.Create;
          try
            try
              Bmp.LoadFromStream(LoadStreamFromUrl(sUrl));
            except
            end;
            TThread.Synchronize(nil,
              procedure
              begin
                AListView.Items[i].Bitmap := Bmp;
              end
            );
          finally
            Bmp.Free;
          end;
        end;
      end;
    end
  ).Start;
end;


With that said, you might consider using Delphi's PPL library to load multiple
images in parallel, instead of a single anonymous thread to load them sequentially:

uses
  ..., System.Threading;
 
procedure TfrmMain.LoadImage(AListView: TListView);
begin
  TParallel.For(0, AListView.Items.Count-1,
    procedure(Index: Integer: State: TParallel.TLoopState)
    var
      sUrl : string;
      myMemoryStream : TMemoryStream;
      Bmp: TBitmap;
    begin
      if State.ShouldExit then Exit;
      TThread.Synchronize(nil,
        procedure
        begin
          sUrl := AListView.Items[Index].Data[TMultiDetailAppearanceNames.Detail3].ToString;
        end
      );
      // then url of image to load
      sUrl := Trim(sUrl);
      sUrl := StringReplace(sUrl, '.tif', '.jpg', [rfReplaceAll]);
      sUrl := TIdUri.urlEncode(sUrl);
      if pos('.jpg', sUrl) > 0 then
      begin
        if State.ShouldExit then Exit;
        Bmp := TBitmap.Create;
        try
          try
            Bmp.LoadFromStream(LoadStreamFromUrl(sUrl));
          except
          end;
          if State.ShouldExit then Exit;
          TThread.Synchronize(nil,
            procedure
            begin
              AListView.Items[Index].Bitmap := Bmp;
            end
          );
        finally
          Bmp.Free;
        end;
      end;
    end
  );
end;


--
Remy Lebeau (TeamB)
Lucio Fiore

Posts: 7
Registered: 2/4/14
Re: Load item image of TListView in Thread
Click to report abuse...   Click to reply to this thread Reply
  Posted: Oct 9, 2015 5:16 AM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
Thanks Remy Lebeau (TeamB),

I tried both solutions but I always have the same problem,
everything works under Windows, on Android does not display images .

After several tests I found the problem :
The problem is the bitmap object, if I create it and I manage within a thread, on Android have an exception of type Bitmap size too big,
but if I load the bitmap in the main thread it all works fine.

So now the problem is how can I load a bitmap in a thread

Thanks
Remy Lebeau (Te...


Posts: 7,318
Registered: 12/23/01
Re: Load item image of TListView in Thread
Click to report abuse...   Click to reply to this thread Reply
  Posted: Oct 9, 2015 7:29 PM   in response to: Lucio Fiore in response to: Lucio Fiore
Lucio wrote:

The problem is the bitmap object, if I create it and I manage within
a thread, on Android have an exception of type Bitmap size too big,

but if I load the bitmap in the main thread it all works fine.

That is a known problem with Firemonkey's TBitmap class up to at least XE7
(I do not know if it has been fixed in XE8/Seattle yet), so you will have
to load the bitmap in the main thread, but you can still download the
bitmap in the worker thread, at least:

procedure TfrmMain.LoadImage(AListView: TListView);
var
  ListItemCount, I: Integer;
  Urls: array of string;
begin
  ListItemCount := AListView.Items.Count;
  SetLength(Urls, ListItemCount);
  for I := 0 to ListItemCount-1 do
    Urls[I] := AListView.Items[I].Data[TMultiDetailAppearanceNames.Detail3].ToString;
 
  TThread.CreateAnonymousThread(
    procedure
    var
      Index: Integer;
      sUrl : string;
      myStream : TStream;
    begin
      for Index := 0 to ListItemCount -1 do
      begin
        sUrl := Urls[Index];
        // then url of image to load
        sUrl := Trim(sUrl);
        sUrl := StringReplace(sUrl, '.tif', '.jpg', [rfReplaceAll]);
        sUrl := TIdUri.urlEncode(sUrl);
        if pos('.jpg', sUrl) > 0 then
        begin
          myStream := LoadStreamFromUrl(sUrl);
          try
            TThread.Synchronize(nil,
              procedure
              var
                Bmp: TBitmap;
              begin
                Bmp := TBitmap.Create;
                try
                  try
                    Bmp.LoadFromStream(myStream);
                  except
                  end;
                  AListView.Items[Index].Bitmap := Bmp;
                finally
                  Bmp.Free;
                end;
              end
            );
          finally
            myStream.Free;
          end;
        end;
      end;
    end
  ).Start;
end;


Or:

uses
  ..., System.Threading;
 
procedure TfrmMain.LoadImage(AListView: TListView);
var
  ListItemCount, I: Integer;
  Urls: array of string;
begin
  ListItemCount := AListView.Items.Count;
  SetLength(Urls, ListItemCount);
  for I := 0 to ListItemCount-1 do
    Urls[I] := AListView.Items[I].Data[TMultiDetailAppearanceNames.Detail3].ToString;
 
  TParallel.For(0, ListItemCount-1,
    procedure(Index: Integer: State: TParallel.TLoopState)
    var
      sUrl : string;
      myStream : TMemoryStream;
    begin
      if State.ShouldExit then Exit;
      sUrl := Urls[Index];
      // then url of image to load
      sUrl := Trim(sUrl);
      sUrl := StringReplace(sUrl, '.tif', '.jpg', [rfReplaceAll]);
      sUrl := TIdUri.urlEncode(sUrl);
      if pos('.jpg', sUrl) > 0 then
      begin
        if State.ShouldExit then Exit;
        myStream := LoadStreamFromUrl(sUrl);
        try
          if State.ShouldExit then Exit;
          TThread.Synchronize(nil,
            procedure
            var
              Bmp: TBitmap;
            begin
              Bmp := TBitmap.Create;
              try
                try
                  Bmp.LoadFromStream(myStream);
                except
                end;
                AListView.Items[Index].Bitmap := Bmp;
              finally
                Bmp.Free;
              end;
            end
          );
        finally
          myStream.Free;
        end;
      end;
    end
  );
end;


In either case, on mobile platforms at least, you can optionally replace
TThread.Synchronize() with TThread.Queue() and let ARC manage the lifetime
of the TStream object (in your original code, you are leaking that object
on Windows):

procedure TfrmMain.LoadImage(AListView: TListView);
var
  ListItemCount, I: Integer;
  Urls: array of string;
begin
  ListItemCount := AListView.Items.Count;
  SetLength(Urls, ListItemCount);
  for I := 0 to ListItemCount-1 do
    Urls[I] := AListView.Items[I].Data[TMultiDetailAppearanceNames.Detail3].ToString;
 
  TThread.CreateAnonymousThread(
    procedure
    var
      Index: Integer;
      sUrl : string;
      myStream : TStream;
    begin
      for Index := 0 to ListItemCount -1 do
      begin
        sUrl := Urls[Index];
        // then url of image to load
        sUrl := Trim(sUrl);
        sUrl := StringReplace(sUrl, '.tif', '.jpg', [rfReplaceAll]);
        sUrl := TIdUri.urlEncode(sUrl);
        if pos('.jpg', sUrl) > 0 then
        begin
          myStream := LoadStreamFromUrl(sUrl);
          TThread.Queue(nil,
            procedure
            var
              Bmp: TBitmap;
            begin
              Bmp := TBitmap.Create;
              try
                Bmp.LoadFromStream(myStream);
              except
              end;
              AListView.Items[Index].Bitmap := Bmp;
            end
          );
        end;
      end;
    end
  ).Start;
end;


Or:

uses
  ..., System.Threading;
 
procedure TfrmMain.LoadImage(AListView: TListView);
var
  ListItemCount, I: Integer;
  Urls: array of string;
begin
  ListItemCount := AListView.Items.Count;
  SetLength(Urls, ListItemCount);
  for I := 0 to ListItemCount-1 do
    Urls[I] := AListView.Items[I].Data[TMultiDetailAppearanceNames.Detail3].ToString;
 
  TParallel.For(0, ListItemCount-1,
    procedure(Index: Integer: State: TParallel.TLoopState)
    var
      sUrl : string;
      myStream : TMemoryStream;
    begin
      if State.ShouldExit then Exit;
      sUrl := Urls[Index];
      // then url of image to load
      sUrl := Trim(sUrl);
      sUrl := StringReplace(sUrl, '.tif', '.jpg', [rfReplaceAll]);
      sUrl := TIdUri.urlEncode(sUrl);
      if pos('.jpg', sUrl) > 0 then
      begin
        if State.ShouldExit then Exit;
        myStream := LoadStreamFromUrl(sUrl);
        if State.ShouldExit then Exit;
        TThread.Queue(nil,
          procedure
          var
            Bmp: TBitmap;
          begin
            Bmp := TBitmap.Create;
            try
              Bmp.LoadFromStream(myStream);
            except
            end;
            AListView.Items[Index].Bitmap := Bmp;
          end
        );
      end;
    end
  );
end;


--
Remy Lebeau (TeamB)
Lucio Fiore

Posts: 7
Registered: 2/4/14
Re: Load item image of TListView in Thread
Click to report abuse...   Click to reply to this thread Reply
  Posted: Oct 12, 2015 7:54 AM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
Thanks Remy Lebeau,

now works fine (I've used the last one)
Legend
Helpful Answer (5 pts)
Correct Answer (10 pts)

Server Response from: ETNAJIVE02