FabDev 8 Posted July 9, 2021 (edited) 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 July 9, 2021 by FabDev Share this post Link to post
f.m 8 Posted July 14, 2021 (edited) 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 July 14, 2021 by f.m Share this post Link to post
Guest Posted July 14, 2021 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 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. 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 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 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
FabDev 8 Posted July 15, 2021 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 Posted July 15, 2021 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
Serge_G 87 Posted July 16, 2021 (edited) 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 July 16, 2021 by Serge_G Share this post Link to post