6

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.

maaartinus
  • 44,714
  • 32
  • 161
  • 320
  • 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 May 04 '18 at 10:06
  • @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 May 04 '18 at 19:23
  • @Hans This would solve it, but how can I make the `TCustomListbox` use `TMyListBoxItem`? – maaartinus May 04 '18 at 19:24
  • @maaartinus I'm not sure you can. As recently as 10.0 Seattle (I don't have a later version installed), use of `TListBoxItem` is hard-coded inside of the `TListBox.Items` implementation, you can't override it. Maybe things have changed in Berlin or Tokyo, I don't know. You will have to check the source code for `TListBox` in `FMX.ListBox.pas` to see if it allows you to specify a user-defined class (but I doubt it, I don't see anything in the [`TListBox` documentation](http://docwiki.embarcadero.com/Libraries/en/FMX.ListBox.TListBox) about that). – Remy Lebeau May 04 '18 at 23:36
  • To set the Text color with my solution, simply use `MyItem.TextLabel.TextSettings.FontColor`. For the speed, does the normal behavior of the TListBox work any faster? – Hans May 15 '18 at 16:32
  • @Hans I didn't measure it yet and I've made some mistakes (as you can see from my edits). Using custom items seems to be slower than using the string list which seems to be slower then VCL which definitely is much slower than HTML. – maaartinus May 15 '18 at 16:37
  • @Hans Concerning speed, see my update. Concerning color, there's no `TListBoxItem.TextLabel` in my version. – maaartinus May 15 '18 at 19:27
  • @maaartinus What I mean is that if you use a custom TListBoxItem like in my answer, then you need to set the TLabel directly, so I refered to the property in my example. It might be possible to link the TLabel closer to the default properties in TListBoxItem, I just did not find a way, and I use the solution I suggest with success in our large app. – Hans May 15 '18 at 21:32
  • @Hans My bad, instead of reading your answer carefully, I only scanned for what I wanted to see. However, it didn't work either and the reason was the same: I didn't set `StyledSettings` and they overrode my color - even when there's no style at all. I didn't even know they exist. The speed problem has a trivial solution: `BeginUpdate / EndUpdate`. My bad again (I'm not very familiar with Delphi and I'm an FMX noob). – maaartinus May 16 '18 at 00:05
  • 1
    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 May 16 '18 at 07:32
  • 1
    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 (https://github.com/Zeus64/alcinoe) – zeus May 16 '18 at 11:13
  • The purpose with SO is to build a reference library of Questions and Answers. It is not a good idea to change the question when there is an answer to the original question, then the answer no longer makes sense. Also, the additional questions you ask are misunderstandings that have been sorted out in the comments, but you have not updated the question to match it. – Hans Feb 08 '19 at 10:09

1 Answers1

2

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.

Hans
  • 2,220
  • 13
  • 33
  • This looks good, but this means that I can't use the standard `TListBox` methods to add items, doesn't it? – maaartinus May 07 '18 at 13:56
  • The standard functions uses an internal TStrings instance, which you modify through the ListBox.Items property. With the solution I made, you just modify your own TStrings instance and then when you are done, apply it to the ListBox. It is not as clean, but at least it allows you to achieve what you want. – Hans May 07 '18 at 15:17
  • If you need to insert an item at a specific position in the list, you can use the `InsertObject` method of TListBox (and eventually also `AddObject` to append). My answer uses a TStringList because that is close to the original behavior, but I do not use it my self. Instead I update the ListBox directly. – Hans May 07 '18 at 16:00