JohnLM 23 Posted 19 hours ago 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 19 hours ago 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 15 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 15 hours ago by Brian Evans Share this post Link to post
Remy Lebeau 1467 Posted 13 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 13 hours ago by Remy Lebeau 1 Share this post Link to post
David Heffernan 2364 Posted 8 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 6 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 6 hours ago by JohnLM updated opening reply. Share this post Link to post
David Schwartz 440 Posted 5 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 5 hours ago by David Schwartz Share this post Link to post
David Heffernan 2364 Posted 4 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 4 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 1467 Posted 1 hour 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. Share this post Link to post
Remy Lebeau 1467 Posted 1 hour 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