Пользовательский рисунок элементов TCustomListbox

Я переписываю компонент VCL, показывающий настроенный TCustomListbox на Firemonkey в Delphi 10.2. При настройке использовалось переопределенное значение DrawItem, в основном добавление отступов и установка цвета текста в зависимости от текста и индекса элемента.

DrawItem сделал это довольно просто, но в FMX вроде бы ничего подобного нет. Я могу переопределить PaintChildren и нарисовать каждый элемент сам, но тогда он выглядит по-другому, и мне самому приходится иметь дело с прокруткой и всем остальным. Я только начинаю работать с FMX, и у пока нет исходников.

  • Есть ли DrawItem замена в FMX? Возможно, я это пропустил.

  • Если нет, как получить необходимую информацию? В основном это прямоугольник, который нужно нарисовать, и в идеале используемый стиль.

Проблемы

Решение Ганса работает, но имеет несколько серьезных проблем:

Цвет

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

PROCEDURE TMyItem.Paint;
BEGIN
  TextSettings.FontColor := TAlphaColorRec.Red;
  INHERITED;
END;

Скорость

Открытие коробки со 180 предметами занимает около двух секунд. Нам нужно такое количество элементов, и их количество на самом деле является причиной, по которой нам нужен настраиваемый блок (мы обеспечиваем фильтрацию, используя TEdit часть нашего компонента). Версия, использующая строки без TMyItem, была быстрее (хотя, вероятно, медленнее, чем версия VCL), но использование этих элементов, похоже, замедляет ее еще больше (это медленнее, чем заполнение списка HTML с аналогичным стилем).

Или что-то другое? Не имея исходников и практически никакой документации, сказать не могу.

Я попытался кэшировать элементы для повторного использования, но это не помогло.

Похоже, что использование настраиваемых элементов на самом деле быстрее, чем использование строк (время в миллисекундах):

nItems String TMyItem
   200    672      12
  2000   5604     267
 20000  97322   18700

Проблема скорости, кажется, накапливается, когда контент изменяется несколько раз. Я использовал FListBox.Items.Clear;, потом попробовал

n := FListBox.Items.Count;
FOR i := 0 TO n-1 DO FListBox.ListItems[n-1-i].Free;

и, наконец, FListBox.Clear;, что имеет наибольший смысл (и которое я нашел последним). Тем не менее, в итоге, кажется, требуется 2 мс на элемент.


person maaartinus    schedule 01.05.2018    source источник
comment
В FMX я сделал это по-другому: переопределил TListBoxItem, где я добавляю пользовательский рисунок, переопределив метод Paint и добавив другие элементы управления (например, текст / метку) в методе Create.   -  person Hans    schedule 04.05.2018
comment
@J ... Я имею в виду Embarcadero® Delphi 10.2 Version 25.0.29899.2631, как написано в About, исправлено. Во всяком случае, я очень запутался в их нумерации (но это не единственное).   -  person maaartinus    schedule 04.05.2018
comment
@Hans: Это решит эту проблему, но как я могу заставить TCustomListbox использовать TMyListBoxItem?   -  person maaartinus    schedule 04.05.2018
comment
@maaartinus Я не уверен, что ты сможешь. Совсем недавно, в версии 10.0 Seattle (у меня не установлена ​​более поздняя версия), использование TListBoxItem жестко запрограммировано внутри реализации TListBox.Items, вы не можете его переопределить. Может, что-то изменилось в Берлине или Токио, я не знаю. Вам нужно будет проверить исходный код TListBox в FMX.ListBox.pas, чтобы узнать, позволяет ли он указать определяемый пользователем класс (но я сомневаюсь в этом, я ничего не вижу в _ 5_ документация об этом).   -  person Remy Lebeau    schedule 05.05.2018
comment
Чтобы установить цвет текста в моем решении, просто используйте MyItem.TextLabel.TextSettings.FontColor. Что касается скорости, нормальное поведение TListBox работает быстрее?   -  person Hans    schedule 15.05.2018
comment
@Hans Я еще не измерял и сделал несколько ошибок (как вы можете видеть из моих правок). Использование настраиваемых элементов кажется медленнее, чем использование списка строк, который кажется медленнее, чем VCL, который определенно намного медленнее, чем HTML.   -  person maaartinus    schedule 15.05.2018
comment
@Hans По поводу скорости смотри мое обновление. Что касается цвета, то в моей версии нет TListBoxItem.TextLabel.   -  person maaartinus    schedule 15.05.2018
comment
@maaartinus Я имею в виду, что если вы используете настраиваемый TListBoxItem, как в моем ответе, вам нужно установить TLabel напрямую, поэтому я сослался на свойство в моем примере. Возможно, удастся связать TLabel ближе к свойствам по умолчанию в TListBoxItem, я просто не нашел способа и успешно использую предлагаемое мной решение в нашем большом приложении.   -  person Hans    schedule 16.05.2018
comment
@Hans Плохо, что вместо того, чтобы внимательно прочитать ваш ответ, я просмотрел только то, что хотел увидеть. Однако это тоже не сработало, и причина была та же: я не установил StyledSettings, и они перекрыли мой цвет - даже когда стиля нет вообще. Я даже не знал, что они существуют. У проблемы скорости есть тривиальное решение: BeginUpdate / EndUpdate. Опять плохо (я не очень знаком с Delphi и я новичок в FMX).   -  person maaartinus    schedule 16.05.2018
comment
В FMX всегда есть стиль. Стиль закрашивает визуальную часть элемента управления. Если вы не укажете стиль, будет использован стиль по умолчанию. Вы можете легко добавлять свои собственные стили. Если все элементы вашего списка идентичны, вы можете определить свой собственный стиль для ListBoxItem. Я сделал это для некоторых своих задач, но я предпочитаю решение в своем ответе, потому что оно очень гибкое.   -  person Hans    schedule 16.05.2018
comment
Без исходного кода firemonkey у вас очень мало шансов сделать что-то хорошее, я даже не знаю, как можно что-то сделать в firemonkey без исходного кода. Лучше всего было бы просто поставить собственный TvertScrollBox и внутри положить много TLayout, а внутри tlayout.paint сделать все, что вам нужно. Если вас беспокоит скорость, используйте элементы управления Alcinoe (github.com/Zeus64/alcinoe)   -  person zeus    schedule 16.05.2018
comment
Цель SO - создать справочную библиотеку вопросов и ответов. Не рекомендуется менять вопрос, когда есть ответ на исходный вопрос, тогда ответ теряет смысл. Кроме того, дополнительные вопросы, которые вы задаете, являются недоразумениями, которые были устранены в комментариях, но вы не обновили вопрос, чтобы он соответствовал.   -  person Hans    schedule 08.02.2019


Ответы (1)


Вот пример того, как это можно сделать. Ключ состоит в том, чтобы установить Parent (настраиваемого) ListBoxItem в ListBox. Это добавит его в список элементов. Я устанавливаю родительский элемент в конструкторе, поэтому мне не нужно делать это (и запоминать) каждый раз, когда я добавляю что-то в список.

type
  tMyListBoxItem = class(TListBoxItem)
  strict private
    fTextLabel: TLabel;
  public
    constructor Create(aOwner: TComponent);
    property TextLabel: TLabel read fTextLabel;
  end;

implementation

constructor tMyListBoxItem.Create(aOwner: TComponent);
begin
  inherited;
  fTextLabel := TLabel.Create(self);
  fTextLabel.Parent := self;
  Assert(aOwner is TFMXObject, 'tMyListBoxItem.Create');
  Parent := TFMXObject(aOwner);
end;

procedure tMyForm.FillListBox(aListBox: TListBox; aStringList: TStringList);
var
  lItem: tMyListBoxItem;
  i: integer;
begin
  aListBox.BeginUpdate; //to avoid repainting for every item added
  aListBox.Clear;
  for i := 0 to aStringList.Count-1 do
  begin
    lItem := tMyListBoxItem.Create(aListBox);
    lItem.TextLabel.Text := aStringList[i];
    lItem.Margins.Left := 20;
  end;
  aListBox.EndUpdate;
end;

Я использую настраиваемые элементы ListBoxItem во многих местах, потому что вы можете иметь ComboBoxes, EditBoxes и все другие элементы управления в ListboxItem. Это открывает очень динамичный (на основе списка) макет экрана, который легко адаптируется ко всем платформам и размерам экрана.

person Hans    schedule 07.05.2018
comment
Выглядит неплохо, но это означает, что я не могу использовать стандартные TListBox методы для добавления элементов, не так ли? - person maaartinus; 07.05.2018
comment
Стандартные функции используют внутренний экземпляр TStrings, который вы изменяете с помощью свойства ListBox.Items. С помощью решения, которое я сделал, вы просто изменяете свой собственный экземпляр TStrings, а затем, когда закончите, применяете его к ListBox. Это не так чисто, но, по крайней мере, позволяет добиться желаемого. - person Hans; 07.05.2018
comment
Если вам нужно вставить элемент в определенную позицию в списке, вы можете использовать метод InsertObject TListBox (и, в конечном итоге, также AddObject для добавления). В моем ответе используется TStringList, потому что это близко к исходному поведению, но я не использую его сам. Вместо этого я обновляю ListBox напрямую. - person Hans; 07.05.2018