Перейти к содержанию
Fire Monkey от А до Я
  • 0

[ListBox] загрузка картинок


Kitty

Вопрос

Здравствуйте. Картинки грузятся с хостинга в потоке. Что не правильно в коде ниже? Картинки появляются только если прокрутить список вверх вниз т.е. после скролирования. Т.е. все грузиться правильно, но картинки появляются только если прокрутить список вверх-вниз после загрузки. Тест в WIN64. Спасибо.

#include <memory>
  
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 = 44; //image heigth
			ListBoxItem->TextSettings->Trimming = TTextTrimming::None;
			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("44x44.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;
}

 

Изменено пользователем Kitty
Ссылка на комментарий

Рекомендуемые сообщения

  • 0

Я [ZuBy] уважаю! Но не слушайте его, в данном случае! ))))

Я с ним не согласен. Точнее - не совсем.

У TListBox есть ряд свои преимуществ! Причем, зачастую, довольно существенных. Которые никакие TListView не перекрывают. По крайне мере пока.

Во-первых - если речь идет о Винде, то это возможность плавного скроллинга. Чего нет у TListView. Не знаю кому как из разрабов, а рядового пользователя это часто бесит - прокрутка "рывками".

Во-вторых - TListBox и его TListBoxItem'ы все же намного проще "рисовать". В смысле - создание своего шаблона пока еще намного проще чем у TListView. Вы можете для TListBox очень просто создавать свои элементы списка.

Несмотря на то, что TListBox существенно "тормознее" TListView, часто его скорости вполне достаточно.

Все, разумеется, зависит от Ваших конечных целей.

А что касается картинок в Итемах TListBox, то нужно просто переопределить ApplyStyle.

Создайте свою процедуру, например ItemApplyStyle;

Присвойте, при создании Итема, свой обработчик. Например
newItem.OnApplyStyleLookup := ItemApplyStyle;
А в ItemApplyStyle сделайте обновление картинки. Типа так:

procedure TContactActions.ItemApplyStyle(Sender: TObject);
var
  StyleObject: TFmxObject;
  lbx : TListBox;
  LI : TListBoxItem;
begin
  LI := Sender as TListBoxItem;
  LI.BeginUpdate;
  try
    StyleObject := LI.FindStyleResource('picture') as TCircle;
    if Assigned(StyleObject) then
      begin
        TCircle(StyleObject).Fill.Bitmap.Bitmap.Canvas.BeginScene();
        try
          TCircle(StyleObject).Fill.Bitmap.Bitmap := ____тут_ваш_битмап__;
        finally
          TCircle(StyleObject).Fill.Bitmap.Bitmap.Canvas.EndScene;
        end;
      end;
  finally
    LI.EndUpdate;
  end;
end;

Разумеется, все проверки и прочее - это уж Вы сами. И в моем примере это TCircle, а у Вас что - сами решите.

Ну и можно для краткости (если там просто TImage) не писать все через StyleObject, а покороче, через StylesData['_имя_.bitmap'] и т.д.

Ну и что на Delphi - не обессудьте! ) Смысл тут простой.

Думаю, что в целом идея ясна.

Ссылка на комментарий
  • 0

Спасибо.

Цитата

Ну и можно для краткости (если там просто TImage) не писать все через StyleObject, а покороче, через StylesData['_имя_.bitmap']

В 10 уроке курсов Ярослава показана похожая ситуация, но для текста. Но я пока не разобралась как инициализировать StylesData для битмапа который в памяти...
Как я поняла надо заменить строку:
ListBoxItem->ItemData->Bitmap->LoadFromStream(WelcomeINI.get());
на нечто похожее на это:
ListBoxItem->StylesData["ItemData.Bitmap"] = TValue::From<TMemoryStream>(WelcomeINI.get());
но это не правильная конструкция.
Покажите, пожалуйста, на паскале как заполнить StylesData картинкой из TMemoryStream, а я затем переделаю для билдера.
Спасибо.

Ссылка на комментарий
  • 0
  • Модераторы

@AlexG я проверил то что вы описали. в принципе работает, но для статики

почему не работает для динамической подгрузке, точней не совсем работает. 

порядок действий

1) получаю с сервера список картинок

2) заполняю текстом Item'ы

3) присваиваю событие OnApplyStyleLookup для Item'а

Спойлер

procedure TForm13.LoadJSON(aLB: TListBox);
var
  aJSON: string;
begin
  TTask.Run(
    procedure
    begin
      aJSON := TmyAPI.Get(cRequest + '1');

      TThread.Synchronize(TThread.CurrentThread,
        procedure
        var
          aJSONData: TmyJSONData;
          I: integer;
          aItem: TListBoxItem;
        begin
          if TmyAPI.IsOK(aJSON) then
          begin
            aJSONData := TJSON.Parse<TmyJSONData>(aJSON);

            for I := Low(aJSONData.struct) to High(aJSONData.struct) do
            begin
              aItem := TListBoxItem.Create(aLB);
              with aItem do
              begin
                Parent := aLB;
                Text := aJSONData.struct[I];
                TagString := cStatic + aJSONData.struct[I];
                OnApplyStyleLookup := ItemApplyStyleLookup;
              end;
            end;
          end;
        end);
    end);
end;

 

4) в событии ItemApplyStyleLookup пишу следующее

procedure TForm13.ItemApplyStyleLookup(Sender: TObject);
begin
  LoadBitmapFromURL((Sender as TListBoxItem).TagString, (Sender as TListBoxItem).ItemData.Bitmap);
end;
Спойлер

photo1.jpg  photo2.jpg

как видим на картинках несколько айтемов не обновились, т.к. они не могут выйти за пределы области (чтобы обнулить стиль и вызвать событие OnApplyStyleLookup)

и при таком подходе все время картинка будет скачиваться из интернета, что не есть хорошо

UPDATE: добавил проект

LB_dload.zip

Ссылка на комментарий
  • 0
2 часа назад, Kitty сказал:

А как все таки итему присвоить битмап через StylesData?

Битмап нужно где-то хранить.
При вызове OnApplyStyleLookup просто обновляйте его. Как в моем примере. Только в Вашем случае это будет TImage (как понимаю).

var
  StyleObject: TFmxObject;
........

  StyleObject := FindStyleResource('picturechat') as TImage;
  if Assigned(StyleObject) then
    try
      TImage(StyleObject).Visible := // TRUE/FALSE
      TImage(StyleObject).OnClick := // Ваш обработчик, если надо
      if not MBitmap.IsEmpty then
        try
          if not GlobalUseGPUCanvas then
            TImage(StyleObject).Bitmap.Canvas.BeginScene;
          TImage(StyleObject).Bitmap := MBitmap; // ваш битмап
        finally
          if not GlobalUseGPUCanvas then
            TImage(StyleObject).Bitmap.Canvas.EndScene;
        end;
    finally
    end;

типа того.

'picturechat' - ваша картинка в стиле Итема

а хранить изображения можно как контейнерах, так и читать из файлов (все достаточно быстро по скорости!)

а вот с подгрузкой из инета - не знаю - Вам самим решать. Оптимально, думаю, загружать в контейнер, предварительно изменив размер до минимально необходимого.

P.S. Забыл про StylesData

можно так:

вместо всего что написано выше, записать просто

StylesData['picturechat.bitmap'] := MBitmap;

 

Изменено пользователем AlexG
Ссылка на комментарий
  • 0
2 часа назад, Kitty сказал:

А как все таки итему присвоить битмап через StylesData?

var
  Img: TImage;
begin
  Img := TImage.Create(nil);
  try
    Img.Bitmap.LoadFromFile(FileName);
    //Img.Bitmap.LoadFromStream();
    Button1.StylesData['Image.MultiResBitmap'] := Img.MultiResBitmap;
  finally
    Img.Free;
  end;
end;

Не самый лучший пример, при том, что в вашем случае работать все-равно не будет, а что конкретнее:

Спойлер

 

 

fmx.png

 

 

bitmap.zip

Изменено пользователем egorea1999
Ссылка на комментарий
  • 0

Чем больше читаю эту свою тему, тем больше не могу разобраться… :(
Ярослав Бровин в своих курсах сказал, что применение OnApplyStyleLookup это сложный путь и проще использовать StylesData. Соответственно я опираюсь на курсы разработчика FMX.

Цитата

Битмап нужно где-то хранить.

У меня конкретно битмап храниться в TMemoryStream.
На форме дефольный ListBox. Как в его итем запихнуть картинку из  TMemoryStream чтобы она появилась сразу, а не после скроллинга?
egorea1999 показывает приблизительное решение, но тут же пишет:

Цитата

 Не самый лучший пример, при том, что в вашем случае работать все-равно не будет,

Тем самым перечеркивая свою рекомендацию.

Как правильно картинку из TMemoryStream запихнуть в итем дефолтного ListBox-а через StylesData чтобы код из первого поста отработал и картинки были видны сразу?
Если мой код из первого поста вставить в код билдера, вы увидите проблему в WIN64, ибо картинки на хостинге лежат и соответственно код живой…

Тяжело разобраться со стилями даже после курсов...

 

Ссылка на комментарий
  • 0
15 минут назад, Kitty сказал:

 

Как правильно картинку из TMemoryStream запихнуть в итем дефолтного ListBox-а через StylesData чтобы код из первого поста отработал и картинки были видны сразу?
Если мой код из первого поста вставить в код билдера, вы увидите проблему в WIN64, ибо картинки на хостинге лежат и соответственно код живой…

Под спойлером я оставил скриншот, там раскрывается проблема.

>Проблема будет при кешировании стиля и восстановлении стиля из хеша

Исправить проблему, в принципе можно, как мне кажется, но вот кому оно нужно, разработчики сами не могут этого сделать, было бы желание...

Я хотел поковырять FMX, но вот желание отпало по некоторым причинам...

Изменено пользователем egorea1999
Ссылка на комментарий
  • 0

Исходя из выше сказанного - каково самое правильное решение для моего случая? ListBox мертв?

Каково решение окончательное и  бесповоротное?

Честно говоря осадок, не очень...

Теперь уверена, что решение есть, но его знают только авторы fmx... :)

Изменено пользователем Kitty
Ссылка на комментарий
  • 0

Значит окончательное решение такое: не взирая на то, что в листбоксе у меня не будет записей больше 50, надо полностью переделывать проект на листвью?

Если это так, то печально... проект полностью готов в плане интерфейса и кода. Стандартное действие - добавление картинки для итема и все надо переделывать...

 

Изменено пользователем Kitty
Ссылка на комментарий
  • 0

Да еклмн - да хоть 100 элементов, хоть 500 - каждый со своей картинкой! Пишите аккуратно и грамотно - и "да прибудет с Вами "сила" (с)

Ничего не тормозит, не глючит, все рисуется замечательно (что касается TListBox)!

Я уже показывал пример. там речь не об этом, но везде TListBox - и в списке контактов и в сообщениях. Потому-что они намнооооого сложнее того (те итемы, с которыми я работаю), что видно. И их реализация на TListView возможна, но она сложнее на порядок.

И еще момент! Скажите - как у Вас называется (в стиле) тот элемент в TListBoxItem, в котором содержится картинка? Не Icon ли случаем? Не помню точно какое название там по умолчанию, а смотреть сейчас лень, но сделайте свое наименование этого TImage, например 'myiconstyle' или еще как, и обновляйте через свой обработчик OnApplyStyleLookup.

З.Ы. И я так понимаю - Вы делаете приложение именно под Win?

З.З.Ы. И ничего переделывать не надо. Просто дайте скрин, или фрагмент скрина вашего приложения, если такое возможно. Визуально увидеть "проблему" - многого стоит. А если сделаете видео - совсем будет ясно. Хотя я и так понимаю - в чем у Вас проблема. И предложенное мной решение - работает.

Не знаю - что там говорил Ярослав, но применение OnApplyStyleLookup - очень простое! Куда проще-то?

Изменено пользователем AlexG
Ссылка на комментарий
  • 0

Я не делаю приложение для Wlndows. Я делаю приложение для Андроид. Но исходя из видео ембаркадеры, надо предварительно оттестировать видео в windows. Собственно это я пытаюсь сделать...На форме дефолтный листбокс...

В windos 64 код код выше не работает... 

Изменено пользователем Kitty
Ссылка на комментарий
  • 0

Под Андроид, все зависит от того - какая именно задача стоит? Цели этого "списка". Исходя из моего опыта, в некоторых случаях не обойтись без TListView (это не значит, что нельзя TListBox, просто у него ряд своих ограничений, и не только по скорости. Например - не очень "адекватная" работа с Touch. В смысле - прокрутка пальцем списка у TListBox не очень оптимизирована...)

Поэтому - если Ваш список имеет не сложные элементы - я бы порекомендовал бы, наверное, переделать его на TListView. Покажите, или более подробно объясните - какие вообще элементы в каждом Итеме? Какие события обрабатываются? Прокрутка, нажатия, и т.д. В каждом Итеме картинки в каком виде? Их размер и положение? Они прямоугольные? И т.д.
Чем больше деталей Вы предоставите - тем что-то более конкретное можно советовать в Вашем случае. Или даже помочь с кодом (в смысле - с примером реализации).

Списки контактов в своей программе я делал - для Винды и Мака - TListBox, а для Андроида - TListView. И разница в коде - была всего в 4 десятка строк в общей сложности.

Повторюсь - у каждого из этих компонентов есть свои преимущества и свои недостатки. Например: TListView - скорость работы, TListBox - более "гибкий" в плане создания любого Итема.

Ссылка на комментарий
  • 0
Цитата

Покажите, или более подробно объясните - какие вообще элементы в каждом Итеме? Какие события обрабатываются? Прокрутка, нажатия, и т.д. В каждом Итеме картинки в каком виде? Их размер и положение? Они прямоугольные? И т.д.

Все картинки прямоугольные 44х44 пиксела. Сам готовый список на картинке, но пока без картинок. Также заполнено свойство detail в имтеме но сделано невидимым.
ListBoxItem->StylesData["detail.visible"] = TValue::From<bool>(false);
там храниться ссылка на сайт. При нажатии на итем переход на сайт.

void __fastcall TForm1::ListBox1ItemClick(TCustomListBox * const Sender, TListBoxItem * const Item)
{
  if(Item->ClassNameIs("TListBoxGroupHeader"))
		return;

  FormWEB = new TFormWEB(this);//показать новую форму
  FormWEB->WebBrowser1->Navigate(Item->ItemData->Detail);
  FormWEB->Show();
}

Кстати заполнение через StylesData тоже не работает.
При нажатии на кнопку ошибка на строке:
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)

Похоже все надо переделывать заново с заменой на TListView. Хотя строк в списке не будет больше 50...

 

pic.jpg

Ссылка на комментарий
  • 0

1. Создайте свой стиль для ListBoxItem, например 'listboxitemMystyle', скопировав стандартный и переобозвав его. В своем стиле Итемов просто сделайте невидимым Detail. Или, что еще лучше, - храните Ваши ссылки в TagString Итема.

2. В Style Designer, при редактировании Вашего listboxitemMystyle, вставьте TImage задайте ему необходимые свойства: выравнивание, размеры, отступы и т.д. (не забудьте сделать "растягивание" или что там Вам требуется - просто проверьте в дизайнере). И задайте Вашему TImage имя, например, 'myimagestyle'.

3. При загрузке изображения я бы рекомендовал сначала загрузить изображение в созданный TBitmap, а потом уже грузить его в наш 'myimagestyle'. А самое оптимальное решение - загрузка изображений в "контейнер", в фоновом режиме, а при окончании загрузки, по событию, - отрисовка уже в самом итеме. В любом случае - сначала в битмап, а потом этот битмап в Итем. Иначе, вероятность того что битмап отрисуется в вашем случае - практически нулевая.

4. Далее, при создании итемов:

procedure ContactList_AddItem(Sender: TObject);
var
  newItem : TListBoxItem;
  newBitmap : TBitmap;
begin
  newItem := TListBoxItem.Create(nil);
  newItem.StyleLooup := 'listboxitemMystyle'; //  хотя будет проще не писать этого, а прописать в ListBox1.DefaultItemStyles.ItemStyle
  newItem.Text := 'текст';
  newItem.TagString  := тут может быть Ваша ссылка
  newBitmap := TBitmap.Create;
  newBitmap.LoadFrom  ....... // грузим картинку
  newItem.TagObject := newBitmap;
  newItem.OnApplyStyleLookup := MyItemApplyStyle;
  ListBox1.AddObject(newItem);
end;

procedure MyItemApplyStyle(Sender: TObject);
var
  LI : TListBoxItem;
  tmpBitmap : TBitmap;
begin
  LI := Sender as TListBoxItem;
  if Assigned(LI) then
  try
    LI.BeginUpdate;

    tmpBitmap := LI.TagObject as TBitmap;
    if Assigned(tmpBitmap) then
      LI.StylesData['myimagestyle.bitmap'] := tmpBitmap;

    //  тут вообще можно обновлять все что угодно в этом Итеме 
  finally
    LI.EndUpdate;
  end;
end;

Собственно все.

Сорри если что не понятно -  писал "от руки", без проверки. Но вроде все просто.

Изменено пользователем AlexG
Ссылка на комментарий
  • 0
В 04.01.2017 в 12:33, AlexG сказал:

А самое оптимальное решение - загрузка изображений в "контейнер", в фоновом режиме, а при окончании загрузки, по событию, - отрисовка уже в самом итеме

Добрый день!

можете дать пример реализации?

Ссылка на комментарий

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить на вопрос...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

  • Последние посетители   0 пользователей онлайн

    • Ни одного зарегистрированного пользователя не просматривает данную страницу
×
×
  • Создать...