JohnLM 23 Posted yesterday at 09:59 PM Specs: Delphi XE7, VCL, windows 7, Dell laptop (i3 core, 2.40GHz) I have been playing around with the Listview control, (via ViewStyle=vsReport) learning how to add items at runtime through code (not using a database/binder), for 100 to a few thousand items. Again, just playing around with throwing together a quick listview of data, and seeing if there is use for it versus going the database dataset route. Note, because I was self-learning about listview from scratch, I did have a lot of trouble figuring out how to populate the listview. And now that I know how to, I am also sharing it here for reference for others struggling to do the same. Below, is the code snippet of how I created the initial fields and then populate them. The ViewStyle should be set to vsReport in the properties section in order to show a table column layout at run time. It works and that's all that matters at this point in this indeviour. procedure TForm1.btnAddClick(Sender: TObject); var itm : TListItem; Col : TListColumn; s : string; et : int64; SW : TStopwatch; begin Col := lv1.Columns.Add; Col.Caption := 'LN'; Col.Alignment := taLeftJustify; Col.Width := 30; Col := lv1.Columns.Add; Col.Caption := 'ItemNo'; Col.Alignment := taLeftJustify; Col.Width := 60; Col := lv1.Columns.Add; Col.Caption := 'Desc'; Col.Alignment := taLeftJustify; Col.Width := 160; setlength(ary,10); // create the array length (1,2,3,4,5,6,7,8,9,0) chars beep; SW := TSTopWatch.StartNew; // start timing it lv1.Items.BeginUpdate; for i := 0 to 25000 do begin // ary := genRandValues; // generate random numbers ie (2,9,8,4,7,5,6,0,1,3) s:=listtostr(ary); // convert array list into a string var ie ('2984756013') itm:=lv1.Items.Add; // create a row itm.Caption := i.ToString(); // add the major field, ie 'LN', thus the populate with variable i as line numbers itm.SubItems.Add(''); itm.SubItems[0]:= s; // itemno ie '2984756013', and so on. itm.SubItems.Add(''); itm.SubItems[1]:= s; // desc ie same, ... end; lv1.Items.EndUpdate; SW.Stop; // finish timing eb1.text:=(intToStr(SW.ElapsedTicks)+' ticks / '+intToStr(SW.ElapsedMilliseconds)+' ms'); beep; end; But the problem I am having is that even when using the .BeginUpdate/.EndUpdate the speed is still a bit slow. For example, to fill a listview with 10,000 elements, it costs me about aprox 1.6 seconds run time, or for 25,000 elements, 7.6 seconds. I had a look at the earlier part of the for/loop, where I am generating the random numbers and then converting them to strings. I REM'ed them out and the time was still the same or ever-so-slightly less, maybe 1.5 seconds for instance. So that does not seem to be a major issue in slowing the listview down. Is this the maximum throughput I can expect from listview? or Is there anything else I can do to speed this up a lot more? Share this post Link to post
Alexander Sviridenkov 363 Posted yesterday at 10:12 PM Use virtual mode (OwnerData) https://docwiki.embarcadero.com/Libraries/Sydney/en/Vcl.ComCtrls.TListView.OwnerData 1 1 Share this post Link to post
Brian Evans 111 Posted 21 hours ago (edited) As well a lot of controls with lists have a BeginUpdate and EndUpdate so you can stop visually updating the control for every change to an item. Wrap things in an Try/Finally to make sure EndUpdate is always called. lv1.items.BeginUpdate; try // add multiple items finally lv1.items.EndUpdate; end; From the help for Vcl.ComCtrls.TListItems.BeginUpdate : Prevents updating of the list view until the EndUpdate method is called. Call BeginUpdate before making multiple changes to the list of items. When all changes are complete, call EndUpdate so that the changes can be reflected on screen. BeginUpdate and EndUpdate prevent excessive redraws and speed processing time when new items are added, deleted, or inserted. Edited 21 hours ago by Brian Evans Share this post Link to post
Remy Lebeau 1468 Posted 20 hours ago (edited) For a non-trivial number of items, it is best to use the TListView in virtual mode. Set the OwnerData property to true, assign an OnData event handler, and then set the TListView.Items.Count property instead of using the TListView.Items.Add() method, eg: type TItemData = record LN: Integer; ItemNo: string; Desc: string; end; private lvItems: array of TItemData; procedure TForm1.btnAddClick(Sender: TObject); var Col : TListColumn; SW : TStopwatch; s : string; i : Integer; begin Col := lv1.Columns.Add; Col.Caption := 'LN'; Col.Alignment := taLeftJustify; Col.Width := 30; Col := lv1.Columns.Add; Col.Caption := 'ItemNo'; Col.Alignment := taLeftJustify; Col.Width := 60; Col := lv1.Columns.Add; Col.Caption := 'Desc'; Col.Alignment := taLeftJustify; Col.Width := 160; SetLength(lvItems, 25000); SetLength(ary,10); // create the array length (1,2,3,4,5,6,7,8,9,0) chars beep; SW := TStopWatch.StartNew; // start timing it for i := Low(lvItems) to High(lvItems) do begin ary := genRandValues; // generate random numbers ie (2,9,8,4,7,5,6,0,1,3) s := ListToStr(ary); // convert array list into a string var ie ('2984756013') lvItems[i].LN := i; lvItems[i].ItemNo := s; lvItems[i].Desc := s; end; lv1.Items.Count := Length(lvItems); SW.Stop; // finish timing eb1.Text := IntToStr(SW.ElapsedTicks) + ' ticks / ' + IntToStr(SW.ElapsedMilliseconds) + ' ms'; beep; end; procedure TForm1.lv1Data(Sender: TObject; Item: TListItem); begin Item.Caption := lvItems[Item.Index].LN.ToString(); // add the major field, ie 'LN', thus the populate with the index as line numbers Item.SubItems.Add(lvItems[Item.Index].ItemNo); // itemno ie '2984756013', and so on. Item.SubItems.Add(lvItems[Item.Index].Desc); // desc ie same, ... end; Edited 19 hours ago by Remy Lebeau 1 Share this post Link to post
David Heffernan 2364 Posted 14 hours ago 6 hours ago, Brian Evans said: As well a lot of controls with lists have a BeginUpdate and EndUpdate so you can stop visually updating the control for every change to an item. Wrap things in an Try/Finally to make sure EndUpdate is always called. lv1.items.BeginUpdate; try // add multiple items finally lv1.items.EndUpdate; end; From the help for Vcl.ComCtrls.TListItems.BeginUpdate : Prevents updating of the list view until the EndUpdate method is called. Call BeginUpdate before making multiple changes to the list of items. When all changes are complete, call EndUpdate so that the changes can be reflected on screen. BeginUpdate and EndUpdate prevent excessive redraws and speed processing time when new items are added, deleted, or inserted. As stated in the OP this is already being used. Virtual mode is the only answer. Share this post Link to post
JohnLM 23 Posted 12 hours ago (edited) Update on this endeavour. . . a solution has been found and is working, thanks to Alexander S. and Remy for their help. After some time, I managed to get the suggested virtual method posted by Remy working successfully, thank you. But my listview is showing some artifacts in the drawing of the rows. I will have to research that later. I will study this method further in order to understand how it works. Oh, and the run time on my old Dell laptop is now 38ms for the 25k, and 72ms for 100k. A huge difference! Thank you @Alexander Sviridenkov, for first suggesting it to me. I did do some research into it right after your post and was trying but failing to figure out how to put it all together. And also to @Remy Lebeau, for the posted code snippet that showed me the way. Much appreciated!! Edited 12 hours ago by JohnLM updated opening reply. Share this post Link to post
David Schwartz 440 Posted 11 hours ago (edited) If it's a TreeView or a ListView, make sure you don't have an OnChange handler wired up -- or if you do, you want to put a flag in it that says: if ignore_this then Exit; And have a var at the top of the implementation section var ignore_this : Boolean; Set it to true before you start your loading up, and false when you finish. Or you could un-hook the OnChange event before you start and replace it when you're finished. Edited 11 hours ago by David Schwartz Share this post Link to post
David Heffernan 2364 Posted 11 hours ago 8 minutes ago, David Schwartz said: If it's a TreeView or a ListView, make sure you don't have an OnChange handler wired up -- or if you do, you want to put a flag in it that says: if ignore_this then Exit; And have a var at the top of the implementation section var ignore_this : Boolean; Set it to true before you start your loading up, and false when you finish. Or you could un-hook the OnChange event before you start and replace it when you're finished. yeah, global variables in your GUI, great idea! Share this post Link to post
zed 14 Posted 11 hours ago Am I right in understanding that we can't switch to such a virtual mode with the standard TTreeView? Is this why Virtual TreeView exists? Share this post Link to post
Remy Lebeau 1468 Posted 7 hours ago 3 hours ago, zed said: Am I right in understanding that we can't switch to such a virtual mode with the standard TTreeView? Is this why Virtual TreeView exists? That is correct. 1 Share this post Link to post
Remy Lebeau 1468 Posted 7 hours ago 5 hours ago, JohnLM said: But my listview is showing some artifacts in the drawing of the rows. Are you custom drawing the TListView? Virtual mode affects only data storage, so it should not cause drawing issues if you are otherwise using default settings. Share this post Link to post
JohnLM 23 Posted 5 hours ago Remy - no, I am not. I believe it has to do with Rowselect set to True. 1) When I move the mouse around to select a row, the last selected row leaves residual "vertical" outlines of the edges. And, if I just move the mouse cursor across them all (without clicking anywhere on the Listview it will remove the artifacts. 2) When it is set to False, there is no artifacts. Only one cell is highlighted and clean. But I prefer the whole row to show as highlighted when scrolling up/down or clicking into it. So far, I've tried removing the Listview and adding a new one with default criteria and only changing the ownerDraw to True. But no difference. Still shows artifacts. And the Checkboxes no longer show. I'm investigating that as well. Share this post Link to post
Remy Lebeau 1468 Posted 3 hours ago 1 hour ago, JohnLM said: I believe it has to do with Rowselect set to True. Can you provide an small example/steps that reproduces this problem? 1 hour ago, JohnLM said: And the Checkboxes no longer show. I'm investigating that as well. How to use Checkboxes in TListView (OwnerData True) in Delphi Share this post Link to post
JohnLM 23 Posted 3 hours ago Update on the issue with the Listview artifacts. . . This has been resolved by setting Doublebufferd to true. And now for the checkboxes. Share this post Link to post
JohnLM 23 Posted 3 hours ago Remy, I am not sure that this issue is in other OS's. My main development system is still Windows 7 and Delphi XE7, though I do have a Win10 laptop with D11.2 and a Win10 tablet with D12.2 but I have never developed projects utilizing the Listview for those OS's/Devices to date. But Win7/XE7 is what I use daily. I very rarely use Windows 10, and never have Win11. I don't even have any devices with Win11. Anyway. All apps I create are under Win7. Maybe the issue does not exist in Win10/11? and/or with D11/12+? I should probably test and see because now I am curious. Share this post Link to post
JohnLM 23 Posted 3 hours ago (edited) 12 minutes ago, JohnLM said: I should probably test and see because now I am curious. I am confirming that the issue is also reproducible under Windows 10. I copied the XE7 compiled app over to a usb flash drive and inserted in my Win10 tablet and ran three versions: debugged/released gave issue while the app with dubblebuffered=true gave no issues. Again, the app was developed under Win7/XE7. Next, I will bring the whole project source code over to the Win10/D12.2 tablet and recompile to see if the issue is still present. Edited 3 hours ago by JohnLM typo Share this post Link to post
JohnLM 23 Posted 2 hours ago After loading the project into Delphi 12.2, the issue is still present. And setting Doublebuffered=true resolves it. Note, these are win32 bit app. Deslphi XE7 does not create win64 bit apps for VCL. At least not in mine, there is no "64-bit Windows" selection at the top in the IDE for the Pro version. I was not able to change the app to a win64 bit one in this project on D12.2 for some reason. So I am going with the assumption that win64 also suffers from this issue. Share this post Link to post