Jump to content
FabDev

TListView first and last item visible

Recommended Posts

Hello,

 

Does someone know how to get first and last item index visible in a TListView ?

 

Purpose : I use TlistView.OnUpdateObjects event to custom draw items and it's work fine. But it is very slow (with a certain number of items) because it is called for each items !

Edited by FabDev

Share this post


Link to post

Hello,

This could be a way to determine the first and last visible items:

 

procedure GetStartEndItemsIndex(const AListView : TListView; out AStartItemIndex : Integer; out AEndItemIndex : Integer);

var

  LViewportStart, LViewportEnd, LItemAbsEnd : Single;

  LItemIndex : Integer;

begin

  AStartItemIndex := -1;

  AEndItemIndex := -1;

 

  LViewportStart := AListView.ScrollViewPos;

  LViewportEnd := AListView.Height + LViewportStart;

 

  for LItemIndex := 0 to AListView.Items.Count - 1 do

  begin

    LItemAbsEnd := AListView.GetItemRect(LItemIndex).Bottom + AListView.ScrollViewPos;

 

    if (AStartItemIndex < 0) and (LItemAbsEnd >= LViewportStart) then

    begin

      AStartItemIndex := LItemIndex;

    end;

    if (AStartItemIndex >= 0) and (AEndItemIndex < 0) and (LItemAbsEnd >= LViewportEnd) then

    begin

      AEndItemIndex := LItemIndex;

      Break;

    end;

  end;

  if (AEndItemIndex < 0) and (AListView.Items.Count > 0) then

  begin

    AEndItemIndex := AListView.Items.Count - 1;

  end;

end;

 

// Example:

procedure TFormMain.ListView1ScrollViewChange(Sender: TObject);

var

  LStartItemIndex, LEndItemIndex : Integer;

begin

  GetStartEndItemsIndex(ListView1, LStartItemIndex, LEndItemIndex);

  Text1.Text := Format('%d, %d', [LStartItemIndex, LEndItemIndex]);

end;

 

Please note that

- this is linear, not suitable if the ListView contains a large number of items;

- when TListView's SearchVisible property is set to True, it will be necessary to change the starting position of the viewport;

 

A better algorithm, which probably also takes into account any animations in progress on mobiles platforms, and make use of binary search, can be found in
procedure TListViewBase.DrawListItems(..)

in FMX.ListView unit, next to the comment:

"// Determine starting and final elements that are currently visible.".

Edited by f.m

Share this post


Link to post
Guest
On 7/9/2021 at 2:23 PM, FabDev said:

Does someone know how to get first and last item index visible in a TListView ?

There is many ways to this, but !

On 7/9/2021 at 2:23 PM, FabDev said:

Purpose : I use TlistView.OnUpdateObjects event to custom draw items and it's work fine. But it is very slow (with a certain number of items) because it is called for each items !

Now we have a problem, and i am sorry for this long post, lets fix the problem at its core

 

1) TListView is Windows system class, and it does manage the item on itself.

2) The system can and will manage it in best way, a way you will waste time and put error prone code to do the things the system is doing better.

3) ListView will not ask you to draw invisible items or items out side of the viewing port !!!!!!!!, it doesn't ask to draw them all , but will notify for all, see the difference, it might as for any item, under moving the mouse, or on resizing.... the is the problem that you think you have to solve.

4) The problem is understanding how it does work with its notification and Delphi documentation is not much of help on this matter, this is the core of the problem. 

 

I didn't google for resources to explain this or just add extra info, but i will demonstrate how to debug your assumption of something when you feel it is wrong, just investigate it !

 

Here my way to get confirmation of what i remember also i do that for what so ever i think it can be better, so lets get information.

 

I dropped a list view and a memo on a form like this

 image.thumb.png.0e8d44c5c12ce7d62b581ceb6c56e0ad.png

And only used two events

procedure TForm12.FormCreate(Sender: TObject);
var
  I: Integer;
begin
  ListView1.Items.BeginUpdate;
  try
    for I := 0 to 500 - 1 do
      with ListView1.Items.Add do
        Caption := IntToStr(I);
  finally
    ListView1.Items.EndUpdate;
  end;
end;

procedure TForm12.ListView1CustomDrawItem(Sender: TCustomListView; Item: TListItem; State:
  TCustomDrawState; var DefaultDraw: Boolean);
var
  Rect: TRect;
begin
  Rect := Item.DisplayRect(drBounds);
  //if (Rect.Top <> Rect.Bottom) and (Rect.Bottom >= 0) then
    Memo1.Lines.Add(IntToStr(Item.Index) + '  Top: ' + IntToStr(Rect.Top) + '  Bottom: ' +
      IntToStr(Rect.Bottom));
end;

 

Now to the result we have when we move the mouse over items, only these items under the mouse cursor will be notified to redraw.

image.thumb.png.d0638f3f917b4985289994fcb98cd50c.png

I moved the mouse over the item 44, yet the system asked for redraw far stuff like 6 and 7...

By scrolling to the end, everything will be clear

image.thumb.png.a0aae2c7adc960bafc9e15619d168ca3.png

Scrolling down gave negative coordinates also draw them all, (backward may be, this is irrelevant), we don't care but we started to understand how it works, right ?

Now lets go back to the top of what we recorded and we will notice this

image.thumb.png.e7a9dae349f4bf5015bce4bf83ba1ccf.png

So the system issued draw notification on all items with zero size rectangle then after that issued draw event on the visible items with dimensions.

 

I hope by now you see the use of the commented line, and how it decreased the draw event to only the visible items, literally the bare minimum of draw code will be executed now.

 

 

My last thought on your approach, try to not draw anything out of the according event, in most cases (almost always) issue an invalidate (on the listview as whole or a part of it like an item), and your draw will do it fast and right.

You might need to rethink your OnUpdateObjects usage accordingly!

 

Hope that help. 

Share this post


Link to post

Thank you for your really interesting answer.

 

Unfortunately TlistView has no CustomDrawItem event on Firemonkey. That why I have used OnUpdateObjects event but it's doesn't work fine...

 

 

And yes the methode GetItemRect  can to the trick :

 

r:Trectf;
...
R := TListView(Sender).GetItemRect( AItem.Index);
...

 

 

I use OnScrollViewChange event to load more event, it's work fine on Windows, but it's slow on MacOS (Without metal), IOS and Android.

 

 

Share this post


Link to post
Guest
1 hour ago, FabDev said:

Unfortunately TlistView has no CustomDrawItem event on Firemonkey.

Ops is not quite enough, i have to visit a doctor for eyes check or brain damage, i am sorry i missed the FMX part !

 

Share this post


Link to post

IMHO if your listview only have items not resized  you can use ScrollViewPos property and perhaps the ScrollViewChange event 

 

var first, last : integer;
begin
first:=Trunc(listview1.ScrollViewPos/listview1.ItemAppearance.ItemHeight);
Last:=First+Trunc(ListView1.Height/listview1.ItemAppearance.ItemHeight);
memo1.lines.add(Format('First %d Last %d',[first,last]));

If you have a TSearchbox visible I think it's easy to take care of it (using Searchbox unit to get the height)

 

If you have groups or variable height items this should be harder but still using ScrollViewPos I think it's playable, you "just" have to calc from top, cumulate all  items height  till reaching ScrollViewPos for first and ScrollViewPos+ListView.Height for last

Edited by Serge_G

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×