Virtual listview, MultiSelect: early reference to Selected object may cause performace problems
- Lazarus/FPC Version: <Lazarus 2.2.0RC1 r65419 FPC 3.2.2 x86_64-win64-win32/win64> (Copy from Menu -> Help -> About Lazarus -> Button "Copy version information to clipboard")
- Operating System: <Windows 10 x64_>
- CPU / Bitness: <64_> (64 or 32 Bit?)
What happens
<The listview changes introduced with 2.2 RC1 may lead to a tremendous performance breakdow when using a virtual listview with heavy populated lists. When retrieving the listview "Selected" object "on a wrong place", this might provoke that an early initialization of all objects at once happens. Attached a use case as sample. In the test sample, the guilty statement placed herein does not more than that: if SLV.Selected = nil then xyz := 'dummy';
Note that this statement, in the test project, obviously seems to not make much sense. But in practice some similiar effect might be buried within some subordinate procedures or functions elsewhee. That are by far not as obvious and easy to identify as here! I can tell that it might be very hard to track down why and where long working programs do fail.
What did you expect
<That a listview select reference does not break code; that a reference to Select does not deactivate the viurtual paradigm. Such an "If Selected" statement might sleep anywhere in the code, where it didn't look or behave suspicious until now! The listview has to be so robust as not to let a program fail by that. With 2.0.12 there had been not such an issue.
Steps to reproduce
<Use teh test project attached (ShellViewEx_virtual_multiselect__test_performance_breakdown.zip)
- Navigate to the folder Windows\system32 or, more impressive, Windows\winsxs and click on it. Notice that it takes forever, were formely it went fast!
- Click on the button for to show how oftenly OnData had been called. If this test project is argued to be too complex, you may try the simpletest version (which will simply show how often OnData will be called) - listview_virtualMode_multiselect_selected_OnData_simpletest.zip.
listview_virtualMode_multiselect_selected_OnData_simpletest.zip
ShellViewEx_virtual_multiselect__test_performance_breakdown.zip
Following a workaround i found to work very fine; commented.
I follewed the idea from GetMem in https://forum.lazarus.freepascal.org/index.php/topic,55398.45.html reply number 45 (customlistview.zip). But i needed two little modifications of it.
customlistview.inc, TCustomListView.GetSelection, lines 1440ff:
else begin
{ according to Delphi docs we always must return first selected item,
not the last selected one see issue #16773 }
if not (lffSelectedValid in FFlags) or MultiSelect then
begin
// modified proposal from GetMem July 28, 2021:
// if FSelectedIdx=-1 then // Solves, but has unwanted side effects for a regular listview!
// if SelCount = 0 then // As alternative. But that would have noticeable slowdown on large lists! (again heavy internal loops)
+ if OwnerData And (FSelectedIdx=-1) then // This appears to work fine and fast .. but needs a little "And"
+ begin
+ Result := nil;
+ FSelected := nil;
+ Exit;
+ end;
FSelected := nil;
for i := 0 to Items.Count - 1 do
begin
if Items[i].Selected then
begin
FSelected := Items[i];
break;
end;
end;
AND
(otherwise Navigation by arrow keys (VK_UP, VK_DOWN) would set listview "Selected" object to nil.
You can check that with the test project attached,
listview_virtualMode_multiselect_selected_behaviour_ArrowKeys.zip
listview_virtualMode_multiselect_selected_behaviour_ArrowKeys.zip):
TCustomListView.CNNotify; line 358ff:
if (nm^.iItem < 0) or (nm^.iItem = FSelectedIdx) then
- //InvalidateSelected;
+ if not MultiSelect then // this prevents the arrow key issue
+ InvalidateSelected;
// Please choose and add the following labels as appropriate: // - "Version::.._" // - "Category::> // - "WS:" if and only if you tested with different Widgetset, and if only one/some is/are affected // - "Severity::Crash" if the issue causes a crash. Otherwise any severity added, will be removed.