6
votes

I'm rewriting a VCL component showing a customized TCustomListbox to Firemonkey in Delphi 10.2. The customization used an overridden DrawItem, basically adding some indentation and setting the text color depending on the item text and index.

DrawItem made it rather easy, but there seem to be nothing like that in FMX. I can override PaintChildren and draw every item myself, but then it looks differently and I have to deal with scrolling and everything myself. I'm just starting with FMX and don't have the sources yet.

  • Is there a DrawItem replacement in FMX? I may have missed it.

  • If not, how do it get the needed information? Basically, the rectangle to draw in and ideally the style used.

Problems

The solution by Hans works, but has some major problems:

Color

Setting the color doesn't work, the text is always black. I tried various possibilities including this one:

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

Speed

Opening a box with 180 Items takes maybe two seconds. We need that many items and their count is actually the reason why we need a customized box (we provide filtering using the TEdit part of our component). A version using strings without TMyItem was faster (though probably slower than the VCL version), but using these items seems to slow it down even more (it's slower than filling an HTML list styled similarly).

Or something else? Having no sources and practically no documentation I can't tell.

I tried to cache the items for reuse, but this didn't help.

It looks like using custom items is actually faster than using strings, (timing in milliseconds):

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

The speed problem seems to accumulate when the content changes multiple times. I was using FListBox.Items.Clear;, then I tried

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

and finally FListBox.Clear;, which makes most sense (and which I found last). Still, in the end it seems to need 2 ms per item.

1
On FMX I have done it in a different way: overriding TListBoxItem where I add custom drawing by overriding the Paint method and add other controls (eg Text/Label) in the Create method. - Hans
@J... I mean "Embarcadero® Delphi 10.2 Version 25.0.29899.2631" as it writes in "About", fixed. Anyway, I'm pretty confused about their numbering (but that's not the only such thing). - maaartinus
@Hans This would solve it, but how can I make the TCustomListbox use TMyListBoxItem? - maaartinus
There is always a style in FMX. The style paints the visual part of the control. If you do not specify a style, then the default style is used. You can easily add your own styles. If all your listbox items are identical, then you could define your own style for a ListBoxItem. I have done that for some of my items, but I prefer the solution in my answer because it is so flexible. - Hans
Without the source code of firemonkey, you have very few chance to make something good, I even don't know how it's possible to do anything in firemonkey without the source code. My best bet would be to simply put a custom TvertScrollBox and inside put many TLayout and inside the tlayout.paint do everything you need. If you are worried about speed, use Alcinoe controls (github.com/Zeus64/alcinoe) - zeus

1 Answers

2
votes

Here is an example of how it can be done. The key is to set the Parent of the (custom) ListBoxItem to the ListBox. This will append it to its list of items. I set the parent in the constructor, so I don't have to do it (and remember it) each time I add something to a 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;

I use custom ListBoxItems in many places now because you can have ComboBoxes, EditBoxes, and all other controls in a ListboxItem. This opens for a very dynamic (list based) screen layout that easily adapts to all platforms and screen sizes.