Page 1 of 2

[Android]TIdHTTP and TThread

PostPosted: Thu Dec 15, 2016 2:51 am
by Lena
Hi.
I plan to have 5-100 lines in the ListBox with pictures from remote host. Please help me to move this code into a separate TThread in C++ Builder Berlin.
Code: Select all
void __fastcall TForm1::Button1Click(TObject *Sender)
{
  std::unique_ptr<TMemoryStream> WelcomeINI(new TMemoryStream());
 //test for 5 images:
  for (int i = 1; i <= 5; i++)
  {
      String NameOfImage = IntToStr(i) + ".png";
      String URLLink = L"http://welcome.um.la/myimg/" + NameOfImage;
      try
      {
      IdHTTP1->Get(URLLink, WelcomeINI.get());
      WelcomeINI->Position = 0;
      }
      catch(...)
         {
          WelcomeINI->LoadFromFile("72x72.png"); //image by default
          WelcomeINI->Position = 0;
         }


     ListBox1->BeginUpdate();
     TListBoxItem *ListBoxItem;
     TListBoxGroupHeader *ListBoxGroupHeader;
     ListBoxGroupHeader = new TListBoxGroupHeader(ListBox1);
     ListBoxGroupHeader->Text = L"Custom header";
     ListBox1->AddObject(ListBoxGroupHeader);

     ListBoxItem = new TListBoxItem(ListBox1);
     ListBoxItem->StyleLookup = L"listboxitemleftdetail";
     ListBoxItem->Height = 72; //image heigth
     ListBoxItem->TextSettings->WordWrap = true;
     ListBoxItem->Text = L"Custom text";
     ListBoxItem->ItemData->Bitmap->LoadFromStream(WelcomeINI.get());
     ListBox1->AddObject(ListBoxItem);
      WelcomeINI->Clear();
     ListBox1->EndUpdate();
  }


}
//---------------------------------------------------------------------------

void __fastcall TForm1::IdHTTP1WorkEnd(TObject *ASender, TWorkMode AWorkMode)
{
 //TAniIndicator
 AniIndicator1->Enabled = false;
}
//---------------------------------------------------------------------------

void __fastcall TForm1::IdHTTP1WorkBegin(TObject *ASender, TWorkMode AWorkMode, __int64 AWorkCountMax)

{
 AniIndicator1->Enabled = true;
}

Re: [Android]TIdHTTP and TThread

PostPosted: Thu Dec 15, 2016 12:34 pm
by rlebeau
Lena wrote:I plan to have 5-100 lines in the ListBox with pictures from remote host. Please help me to move this code into a separate TThread in C++ Builder Berlin.


What is the actual problem you are having? Have you ever worked with TThread or TTask before? I've written examples for this kind of scenario before, for example this one (sorry, it is in Delphi, but it can be translated to C++).

I can tell you right now, though, that FireMonkey's TBitmap class DOES NOT work correctly outside of the main UI thread. There are many open bug tickets about that. So, you could certainly download the images in a background thread, but you would end up doing a lot of synchronizing with the UI thread in order to load them into the UI.

Re: [Android]TIdHTTP and TThread

PostPosted: Fri Dec 16, 2016 2:19 am
by Lena
What is the actual problem you are having?


TIdHTTP, TBitmap with TThread this is a very difficult subject for me.
Thank You for Your code but for me very difficult translates for C++. I did not find in the Help example in C++.

that FireMonkey's TBitmap class DOES NOT work correctly


I read on a forum that can be used TBitmapSurface. However, there is no example in C ++. :(

Re: [Android]TIdHTTP and TThread

PostPosted: Wed Dec 21, 2016 5:07 pm
by rlebeau
Try something like this:

Code: Select all
int ThreadsRunning = 0;

void __fastcall TForm1::Button1Click(TObject *Sender)
{
    TListBoxItem *ListBoxItem;
    TListBoxGroupHeader *ListBoxGroupHeader;

    AniIndicator1->Enabled = true;

    ListBox1->BeginUpdate();
    try
    {
        //test for 5 images:
        for (int i = 1; i <= 5; i++)
        {
            ListBoxGroupHeader = new TListBoxGroupHeader(ListBox1);
            ListBoxGroupHeader->Text = L"Custom header";
            ListBox1->AddObject(ListBoxGroupHeader);

            ListBoxItem = new TListBoxItem(ListBox1);
            ListBoxItem->StyleLookup = L"listboxitemleftdetail";
            ListBoxItem->Height = 72; //image heigth
            ListBoxItem->TextSettings->WordWrap = true;
            ListBoxItem->Text = L"Custom text";
            ListBox1->AddObject(ListBoxItem);

            String URLLink = L"http://welcome.um.la/myimg/" + IntToStr(i) + ".png";
            TThread *thread = TThread::CreateAnonymousThread(
              [URLLink, ListBoxItem]()
              {
                  std::unique_ptr<TMemoryStream> WelcomeINI(new TMemoryStream);
                  std::unique_ptr<TIdHTTP> HTTP(new TIdHTTP);

                  try
                  {
                      HTTP->Get(URLLink, WelcomeINI.get());
                  }
                  catch(...)
                  {
                      WelcomeINI->LoadFromFile("72x72.png"); //image by default
                  }

                  WelcomeINI->Position = 0;
                  ListBoxItem->ItemData->Bitmap->LoadFromStream(WelcomeINI.get());
              }
            );
            thread->OnTerminate = &ThreadTerminated;
            thread->Start();

            ++ThreadsRunning;
        }
    }
    __finally
    {
        ListBox1->EndUpdate();
    }
}

void __fastcall TForm1::ThreadTerminated(TObject *Sender)
{
    if (--ThreadsRunning <= 0)
        AniIndicator1->Enabled = false;
}

Re: [Android]TIdHTTP and TThread

PostPosted: Sun Dec 25, 2016 2:47 am
by Lena
Thank You very much!

I have a problem compiling:
WelcomeINI->Position = 0;//here
[bccaarm Error] UnitHTTP.cpp(60): 'this' cannot be implicitly captured in this context

Re: [Android]TIdHTTP and TThread

PostPosted: Sun Dec 25, 2016 2:52 am
by Lena
I try:
Code: Select all
TThread *thread = TThread::CreateAnonymousThread([URLLink, ListBoxItem, this]()


Now missing error. I try to move on. :)

Re: [Android]TIdHTTP and TThread

PostPosted: Sun Dec 25, 2016 3:09 am
by Lena
I have 5 images on hosting:
http://welcome.um.la/myimg/1.png
http://welcome.um.la/myimg/2.png
****
I try on WIN64:
Code: Select all
TListBoxItem *ListBoxItem;
    TListBoxGroupHeader *ListBoxGroupHeader;

    AniIndicator1->Enabled = true;

    ListBox1->BeginUpdate();
    try
    {
        //test for 5 images:
        for (int i = 1; i <= 5; i++)
        {
            ListBoxGroupHeader = new TListBoxGroupHeader(ListBox1);
            ListBoxGroupHeader->Text = L"Custom header";
            ListBox1->AddObject(ListBoxGroupHeader);

            ListBoxItem = new TListBoxItem(ListBox1);
            ListBoxItem->StyleLookup = L"listboxitemleftdetail";
            ListBoxItem->Height = 72; //image heigth
            ListBoxItem->TextSettings->WordWrap = true;
            ListBoxItem->Text = L"Custom text";
            ListBox1->AddObject(ListBoxItem);

         String URLLink = L"http://welcome.um.la/myimg/" + IntToStr(i) + ".png";
         TThread *thread = TThread::CreateAnonymousThread(
         [URLLink, ListBoxItem, this]()
           {
                  std::unique_ptr<TMemoryStream> WelcomeINI(new TMemoryStream);
                  std::unique_ptr<TIdHTTP> HTTP(new TIdHTTP);

                  try
                  {
                 HTTP->Get(URLLink, WelcomeINI.get());
              }
                  catch(...)
                  {
                      WelcomeINI->LoadFromFile("72x72.png"); //image by default
              }

              WelcomeINI->Position = 0;
              ListBoxItem->ItemData->Bitmap->LoadFromStream(WelcomeINI.get());
           }
         );
         thread->OnTerminate = &ThreadTerminated;
            thread->Start();

            ++ThreadsRunning;
        }
    }
    __finally
    {
        ListBox1->EndUpdate();
   }


Strange problems with pictures in ListBox. Please tell me how to fix?

Re: [Android]TIdHTTP and TThread

PostPosted: Thu Dec 29, 2016 8:02 am
by Lena
Trying to add the line of code did not help:
****
ListBox1->AddObject(ListBoxItem);
ListBoxItem->ApplyStyleLookup();//<- here
***

:(

Re: [Android]TIdHTTP and TThread

PostPosted: Fri Dec 30, 2016 8:19 am
by Lena
Hi.
Set StylesData should solve the problem.
As I understand it is necessary to replace the line:
ListBoxItem->ItemData->Bitmap->LoadFromStream(WelcomeINI.get());
something like this:
ListBoxItem->StylesData["ItemData.Bitmap"] = TValue::From<TMemoryStream>(WelcomeINI.get());
but this is not the correct code.
Please show me how to set StylesData a picture from a TMemoryStream.

Re: [Android]TIdHTTP and TThread

PostPosted: Tue Jan 03, 2017 5:26 pm
by rlebeau
Lena wrote:I have a problem compiling:
WelcomeINI->Position = 0;//here
[bccaarm Error] UnitHTTP.cpp(60): 'this' cannot be implicitly captured in this context


That does not make any sense, as nothing inside the lambda I gave you uses the 'this' pointer at all, so it should not need to be captured. What code is on line 60 exactly?

Re: [Android]TIdHTTP and TThread

PostPosted: Tue Jan 03, 2017 5:30 pm
by rlebeau
Lena wrote:Set StylesData should solve the problem.
As I understand it is necessary to replace the line:
ListBoxItem->ItemData->Bitmap->LoadFromStream(WelcomeINI.get());
something like this:
ListBoxItem->StylesData["ItemData.Bitmap"] = TValue::From<TMemoryStream>(WelcomeINI.get());
but this is not the correct code.
Please show me how to set StylesData a picture from a TMemoryStream.


StylesData[] returns a TValue, so I would expect it to return a TBitmap object pointer in this situation:

Code: Select all
ListBoxItem->StylesData["ItemData.Bitmap"].AsType<TBitmap*>()->LoadFromStream(WelcomeINI.get());


However, based on this article:

Working with StylesData in your FireUI applications

It seems you should be using ListBoxItem->FindStyleResource() instead of ListBoxItem->StyledData[] directly.

Re: [Android]TIdHTTP and TThread

PostPosted: Wed Jan 04, 2017 1:49 am
by Lena
What code is on line 60 exactly?


I try:
Code: Select all
TThread *thread = TThread::CreateAnonymousThread([URLLink, ListBoxItem]()
//***
catch(...)
{
WelcomeINI->LoadFromFile("44x44.png"); //image by default
}
WelcomeINI->Position = 0;//<- Failed


[bccaarm Error] UnitHTTP.cpp(60): 'this' cannot be implicitly captured in this context
Failed


I try:
TThread *thread = TThread::CreateAnonymousThread([URLLink, ListBoxItem, this]()
Now missing error. C++ Builder 10.1 Up2.

When press button error on line.
ListBoxItem->StylesData["ItemData.Bitmap"].AsType<TBitmap*>()->LoadFromStream(WelcomeINI.get());
First chance exception at $00000000018485B8. Exception class $C0000005 with message 'c0000005 ACCESS_VIOLATION'.
Process Project1.exe (4624)

Before Android I try in WIN64.
Maybe we have to change TListBox to TListView?

Re: [Android]TIdHTTP and TThread

PostPosted: Wed Jan 04, 2017 2:12 pm
by rlebeau
Lena wrote:I try:
Code: Select all
TThread *thread = TThread::CreateAnonymousThread([URLLink, ListBoxItem]()
//***
catch(...)
{
WelcomeINI->LoadFromFile("44x44.png"); //image by default
}
WelcomeINI->Position = 0;//<- Failed


[bccaarm Error] UnitHTTP.cpp(60): 'this' cannot be implicitly captured in this context
Failed



This sounds like a compiler error to me. As I said, the lambda I gave you does not use the 'this' pointer at all, so I don't know why it is complaining about capturing the 'this' pointer.

Lena wrote:When press button error on line.
ListBoxItem->StylesData["ItemData.Bitmap"].AsType<TBitmap*>()->LoadFromStream(WelcomeINI.get());
First chance exception at $00000000018485B8. Exception class $C0000005 with message 'c0000005 ACCESS_VIOLATION'.
Process Project1.exe (4624)


Did you make sure StylesData[] is not returning an empty TValue?

Code: Select all
TValue value = ListBoxItem->StylesData["ItemData.Bitmap"];
if (!value.IsEmpty)
{
    TBitmap *bitmap = value.AsType<TBitmap*>();
    bitmap->LoadFromStream(WelcomeINI.get());
}


Which line actually crashes? Is it the conversion of the TValue to a TBitmap* pointer, or the call to LoadFromStream(). This is basic debugging.

Re: [Android]TIdHTTP and TThread

PostPosted: Thu Jan 05, 2017 4:12 am
by Lena
Thank You!

Now I see a very strange behavior. All my code now:
Code: Select all
//Try in WIN64 for test
/*
my images here:
http://welcome.um.la/myimg/1.png
http://welcome.um.la/myimg/2.png
***
*/
#include <fmx.h>
#pragma hdrstop
#include <memory>
#include "UnitHTTP.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.fmx"
TForm1 *Form1;

int ThreadsRunning = 0;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
   : TForm(Owner)
{

}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{

   TListBoxItem *ListBoxItem;
   TListBoxGroupHeader *ListBoxGroupHeader;

   AniIndicator1->Enabled = true;

   ListBox1->BeginUpdate();
   try
   {
      //test for 5 images:
      for (int i = 1; i <= 5; i++)
      {
         ListBoxGroupHeader = new TListBoxGroupHeader(ListBox1);
         ListBoxGroupHeader->Text = L"Custom header";
         ListBox1->AddObject(ListBoxGroupHeader);

         ListBoxItem = new TListBoxItem(ListBox1);
         ListBoxItem->StyleLookup = L"listboxitemleftdetail";
         ListBoxItem->Height = 44; //image heigth
         ListBoxItem->TextSettings->Trimming = TTextTrimming::None;
         ListBoxItem->TextSettings->WordWrap = true;
         ListBoxItem->Text = L"Custom text";
         ListBox1->AddObject(ListBoxItem);
         ListBoxItem->ApplyStyleLookup();

         String URLLink = L"http://welcome.um.la/myimg/" + IntToStr(i) + ".png";
         TThread *thread = TThread::CreateAnonymousThread(
         [URLLink, ListBoxItem, this]()
           {
              std::unique_ptr<TMemoryStream> WelcomeINI(new TMemoryStream);
              std::unique_ptr<TIdHTTP> HTTP(new TIdHTTP);

                  try
              {
                 HTTP->Get(URLLink, WelcomeINI.get());
              }
              catch(...)
              {
                 WelcomeINI->LoadFromFile("44x44.png"); //image by default
              }

              WelcomeINI->Position = 0;

              TValue value = ListBoxItem->StylesData["ItemData.Bitmap"];
              if (!value.IsEmpty)
               {
                  TBitmap *bitmap = value.AsType<TBitmap*>();
                  bitmap->LoadFromStream(WelcomeINI.get());
               }

              //ListBoxItem->StylesData["ItemData.Bitmap"].AsType<TBitmap*>()->LoadFromStream(WelcomeINI.get());
              //ListBoxItem->ItemData->Bitmap->LoadFromStream(WelcomeINI.get());

           }
         );
         thread->OnTerminate = &ThreadTerminated;
         thread->Start();

         ++ThreadsRunning;
      }
   }
    __finally
   {
      ListBox1->EndUpdate();
   }

}
//---------------------------------------------------------------------------
void __fastcall TForm1::ThreadTerminated(TObject *Sender)
{
    if (--ThreadsRunning <= 0)
      AniIndicator1->Enabled = false;
}


I set a breakpoint on line and I see an empty bitmap:
TBitmap *bitmap = value.AsType<TBitmap*>(); //breakpoint does not work

I change the code:
Code: Select all
TValue value = ListBoxItem->StylesData["ItemData.Bitmap"];
              if (!value.IsEmpty)
               {
                  TBitmap *bitmap = value.AsType<TBitmap*>();
                  bitmap->LoadFromStream(WelcomeINI.get());
               }


To:
ListBoxItem->ItemData->Bitmap->LoadFromStream(WelcomeINI.get());


After that I see the pictures on the ListBox but only after scrolling.

Probably FMX ListBox has a bug with the cache, and probably should be used instead of a ListBox to TListView...
I do not see any other way. :(
I wanted to deal with the work ListBox but it's not working.

Re: [Android]TIdHTTP and TThread

PostPosted: Thu Jan 05, 2017 12:29 pm
by rlebeau
Lena wrote:Now I see a very strange behavior. All my code now:


Your anonymous thread is not synchronizing with the main UI thread when accessing the ListBoxItem. You MUST synchronize. Not only because it is the right thing to do in general (you can't access UI controls across thread boundaries, especially on Android), but also because FireMonkey's TBitmap class is not thread-safe, it can only be used safely in the main UI thread (there are numerous bug reports in Quality Portal about this issue).

Lena wrote:I set a breakpoint on line and I see an empty bitmap:
TBitmap *bitmap = value.AsType<TBitmap*>(); //breakpoint does not work

I change the code:
Code: Select all
TValue value = ListBoxItem->StylesData["ItemData.Bitmap"];
if (!value.IsEmpty)
   {
      TBitmap *bitmap = value.AsType<TBitmap*>();
      bitmap->LoadFromStream(WelcomeINI.get());
   }


To:
Code: Select all
ListBoxItem->ItemData->Bitmap->LoadFromStream(WelcomeINI.get());


After that I see the pictures on the ListBox but only after scrolling.


That is likely related to you accessing the ListBoxItem and Bitmap from outside of the main UI thread. Sync with the main UI thread when loading the Bitmap, and see if it redraws correctly. If not, try calling ListBoxItem->InvalidateRect() or ListBoxItem->Repaint() after loading the bitmap.