Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom drawing of TCustomListbox items

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.

like image 368
maaartinus Avatar asked May 01 '18 14:05

maaartinus


1 Answers

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.

like image 54
Hans Avatar answered Nov 06 '22 21:11

Hans