View Issue Details

IDProjectCategoryView StatusLast Update
0038565LazarusLCLpublic2021-03-21 09:38
ReporterRemko Plantenga Assigned ToJuha Manninen  
PrioritynormalSeveritymajorReproducibilityalways
Status resolvedResolutionfixed 
Product Version2.0.12 
Summary0038565: TCustomListView.GetNextItem never returns nil if search direction = sdAll and multiple items are selected
DescriptionAccording to the documentation, GetNextItem returns the next selected item for the current selected item. After all items have been enumerated, the function will return nil.

However, if multiple items are selected, the current implementation will always return another item and never return nil.
Steps To Reproduce1) Set MultiSelect property of a ListView to true
2) Use the following simple example from the API documentation: http://docwiki.embarcadero.com/CodeExamples/Sydney/en/GetNextItem_(Delphi)
3) Item will never be nil and thus infinite loop
Additional InformationThe attached example project re-creates the error. Just select both items from the list and click on the button.
TagsNo tags attached.
Fixed in Revisionr64746, r64848
LazTarget-
WidgetsetWin32/Win64
Attached Files

Activities

Remko Plantenga

2021-03-01 22:11

reporter  

GetNextItem.zip (366,566 bytes)

Juha Manninen

2021-03-05 10:58

developer   ~0129395

Please test with r64746.
Iteration started again from the top of list with sdAll. I removed it. Does it break some existing code? Not sure.
The change does not affect directions sdAbove and sdBelow. Their logic looks flawed to me as well.

Remko Plantenga

2021-03-07 14:55

reporter   ~0129479

Thanks for your commit! This works for my example, also not sure if this breaks any existing code. From what I get from the documentation, the GetSelection should always return the first item in the list and GetNextItem should never loop. But I wonder what the difference between sdAll (LVNI_ALL) and sdBelow (LVNI_BELOW) is supposed to be?

And I'm new to the LCL, but I would have guessed that this code redirects to TWSCustomListViewClass and uses the Win32 API (at least on Windows platform) to solve this problem. Why is the implementation manually for all platforms here? Would it make sense to change that or does that require major changes to the current class? I would love to help out in any way I can.

Juha Manninen

2021-03-07 16:37

developer   ~0129488

Yes, extending and calling TWSCustomListViewClass is a good idea. The generic LCL implementation can be there for other widgetsets until they get implemented.
Please study the existing widgetset methods and then provide a patch.
For detailed questions use the mailing list or forum. I am not a WinAPI expert, my development OS is Linux.

Remko Plantenga

2021-03-11 22:38

reporter   ~0129585

Please see the attached patch based on r64785

TWSCustomListView now provides GetNextItem as virtual function and TWin32WSCustomListView overrides this with a simple wrapper for the Win32 API.

I moved the current implementation for GetNextItem to TWSCustomListView, but had to make TListItem.GetState public and add a property States, to still access required data from TListItem.
mypatch.diff (7,928 bytes)   
Index: lcl/comctrls.pp
===================================================================
--- lcl/comctrls.pp	(revision 64785)
+++ lcl/comctrls.pp	(working copy)
@@ -1014,7 +1014,6 @@
     function GetLeft: Integer;
     function GetListView: TCustomListView;
     function GetPosition: TPoint;
-    function GetState(const ALisOrd: Integer): Boolean;
     function GetImageIndex: TImageIndex; virtual;
     function GetIndex: Integer; virtual;
     function GetStateIndex: TImageIndex; virtual;
@@ -1045,6 +1044,7 @@
     function GetCheckedInternal: Boolean;
     function GetOwner: TPersistent; override;
   public
+    function GetState(const ALisOrd: Integer): Boolean;
     procedure Assign(ASource: TPersistent); override;
 
     constructor Create(AOwner: TListItems); virtual;
@@ -1069,6 +1069,7 @@
     property Position: TPoint read GetPosition write SetPosition;
     property Selected: Boolean index Ord(lisSelected) read GetState write SetState;
     property StateIndex: TImageIndex read GetStateIndex write SetStateIndex;
+    property States: TListItemStates read FStates;
     property SubItems: TStrings read GetSubItems write SetSubItems;
     property SubItemImages[const AIndex: Integer]: Integer read GetSubItemImages write SetSubItemImages;
     property Top: Integer read GetTop write SetTop;
Index: lcl/include/customlistview.inc
===================================================================
--- lcl/include/customlistview.inc	(revision 64785)
+++ lcl/include/customlistview.inc	(working copy)
@@ -1345,57 +1345,10 @@
 
 function TCustomListView.GetNextItem(StartItem: TListItem;
   Direction: TSearchDirection; States: TListItemStates): TListItem;
-var
-  ACount: Integer;
-  StartIndex, AIndex: Integer;
-
-  {TODO: create public property States which reads states of item}
-  function GetItemStatesInternal(AItem: TListItem): TListItemStates;
-  begin
-    Result := [];
-    if AItem.GetState(Ord(lisCut)) then ;
-    if AItem.GetState(Ord(lisDropTarget)) then ;
-    if AItem.GetState(Ord(lisSelected)) then ;
-    if AItem.GetState(Ord(lisFocused)) then ;
-    Result := AItem.FStates;
-  end;
-
 begin
   Result := nil;
-  if StartItem = nil then
-    Exit;
-  StartIndex := StartItem.Index;
-  AIndex := StartIndex;
-  ACount := Items.Count;
-  case Direction of
-    sdAbove:
-      while AIndex>0 do
-      begin
-        dec(AIndex);
-        if States <= GetItemStatesInternal(Items[AIndex]) then
-          Exit(Items[AIndex]);
-      end;
-    sdBelow:
-      while AIndex < ACount-1 do
-      begin
-        inc(AIndex);
-        if States <= GetItemStatesInternal(Items[AIndex]) then
-          Exit(Items[AIndex]);
-      end;
-    sdAll:
-      while True do
-      begin
-        inc(AIndex);
-        Assert(AIndex <> StartIndex, 'TCustomListView.GetNextItem: AIndex=StartIndex');
-        if AIndex >= ACount then
-          Exit;
-{       begin           Do not wrap around. Will never return Nil. Issue #38565.
-          AIndex := -1;  continue;
-        end;  }
-        if States <= GetItemStatesInternal(Items[AIndex]) then
-          Exit(Items[AIndex]);
-      end;
-  end;
+  if HandleAllocated then
+     Result := TWSCustomListViewClass(WidgetSetClass).GetNextItem(Self, StartItem, Direction, States);
 end;
 
 procedure TCustomListView.ClearSelection;
Index: lcl/interfaces/win32/win32wscomctrls.pp
===================================================================
--- lcl/interfaces/win32/win32wscomctrls.pp	(revision 64785)
+++ lcl/interfaces/win32/win32wscomctrls.pp	(working copy)
@@ -193,6 +193,8 @@
       const ASortDirection: TSortDirection); override;
     class procedure SetViewOrigin(const ALV: TCustomListView; const AValue: TPoint); override;
     class procedure SetViewStyle(const ALV: TCustomListView; const Avalue: TViewStyle); override;
+
+    class function GetNextItem(const ALV: TCustomListView; const StartItem: TListItem; const Direction: TSearchDirection; const States: TListItemStates): TListItem; override;
   end;
 
   { TWin32WSListView }
Index: lcl/interfaces/win32/win32wscustomlistview.inc
===================================================================
--- lcl/interfaces/win32/win32wscustomlistview.inc	(revision 64785)
+++ lcl/interfaces/win32/win32wscustomlistview.inc	(working copy)
@@ -1435,4 +1435,32 @@
   Windows.InvalidateRect(AHandle, nil, true);
 end;
 
+class function TWin32WSCustomListView.GetNextItem(const ALV: TCustomListView; const StartItem: TListItem;
+  const Direction: TSearchDirection; const States: TListItemStates): TListItem;
+var
+  Flags, Index: Integer;
+begin
+  Result := nil;
 
+  if not WSCheckHandleAllocated(ALV, 'GetNextItem')
+  then Exit;
+
+  Flags := 0;
+  case Direction of
+    sdAbove: Flags := LVNI_ABOVE;
+    sdBelow: Flags := LVNI_BELOW;
+    sdLeft: Flags := LVNI_TOLEFT;
+    sdRight: Flags := LVNI_TORIGHT;
+    sdAll: Flags := LVNI_ALL;
+  end;
+
+  if StartItem <> nil then Index := StartItem.Index else Index := -1;
+
+  if lisCut in States then Flags := Flags or LVNI_CUT;
+  if lisDropTarget in States then Flags := Flags or LVNI_DROPHILITED;
+  if lisFocused in States then Flags := Flags or LVNI_FOCUSED;
+  if lisSelected in States then Flags := Flags or LVNI_SELECTED;
+
+  Index := ListView_GetNextItem(ALV.Handle, Index, Flags);
+  if Index <> -1 then Result := ALV.Items[Index];
+end;
Index: lcl/widgetset/wscomctrls.pp
===================================================================
--- lcl/widgetset/wscomctrls.pp	(revision 64785)
+++ lcl/widgetset/wscomctrls.pp	(working copy)
@@ -172,6 +172,8 @@
       const ASortDirection: TSortDirection); virtual;
     class procedure SetViewOrigin(const ALV: TCustomListView; const AValue: TPoint); virtual;
     class procedure SetViewStyle(const ALV: TCustomListView; const Avalue: TViewStyle); virtual;
+
+    class function GetNextItem(const ALV: TCustomListView; const StartItem: TListItem; const Direction: TSearchDirection; const States: TListItemStates): TListItem; virtual;
   end;
 
   TWSCustomListViewClass = class of TWSCustomListView;
@@ -783,6 +785,61 @@
 
 end;
 
+//Default implementation
+class function TWSCustomListView.GetNextItem(const ALV: TCustomListView;
+  const StartItem: TListItem; const Direction: TSearchDirection; const States: TListItemStates): TListItem;
+var
+  ACount: Integer;
+  StartIndex, AIndex: Integer;
+
+  {TODO: create public property States which reads states of item}
+  function GetItemStatesInternal(AItem: TListItem): TListItemStates;
+  begin
+    Result := [];
+    if AItem.GetState(Ord(lisCut)) then ;
+    if AItem.GetState(Ord(lisDropTarget)) then ;
+    if AItem.GetState(Ord(lisSelected)) then ;
+    if AItem.GetState(Ord(lisFocused)) then ;
+    Result := AItem.States;
+  end;
+begin
+  Result := nil;
+  if StartItem = nil then
+    Exit;
+  StartIndex := StartItem.Index;
+  AIndex := StartIndex;
+  ACount := ALV.Items.Count;
+  case Direction of
+    sdAbove:
+      while AIndex>0 do
+      begin
+        dec(AIndex);
+        if States <= GetItemStatesInternal(ALV.Items[AIndex]) then
+          Exit(ALV.Items[AIndex]);
+      end;
+    sdBelow:
+      while AIndex < ACount-1 do
+      begin
+        inc(AIndex);
+        if States <= GetItemStatesInternal(ALV.Items[AIndex]) then
+          Exit(ALV.Items[AIndex]);
+      end;
+    sdAll:
+      while True do
+      begin
+        inc(AIndex);
+        Assert(AIndex <> StartIndex, 'TWSCustomListView.GetNextItem: AIndex=StartIndex');
+        if AIndex >= ACount then
+          Exit;
+{       begin           Do not wrap around. Will never return Nil. Issue #38565.
+          AIndex := -1;  continue;
+        end;  }
+        if States <= GetItemStatesInternal(ALV.Items[AIndex]) then
+          Exit(ALV.Items[AIndex]);
+      end;
+  end;
+end;
+
 { TWSProgressBar }
 
 class procedure TWSProgressBar.ApplyChanges(const AProgressBar: TCustomProgressBar);
mypatch.diff (7,928 bytes)   

Juha Manninen

2021-03-12 11:27

developer   ~0129601

I went through the patch briefly and it looks good. One note though :
The default GetNextItem implementation in wscomctrls.pp has

+ {TODO: create public property States which reads states of item}
+ function GetItemStatesInternal(AItem: TListItem): TListItemStates;
+ begin
+ Result := [];
+ if AItem.GetState(Ord(lisCut)) then ;
+ if AItem.GetState(Ord(lisDropTarget)) then ;
+ if AItem.GetState(Ord(lisSelected)) then ;
+ if AItem.GetState(Ord(lisFocused)) then ;
+ Result := AItem.States;
+ end;

However you already created a property States. Could it be used here?

Remko Plantenga

2021-03-12 21:49

reporter   ~0129615

Thanks!

So, I'm a little bit unsure of that function. My understanding is: we don't know if FStates is up-to-date, so we call GetState with every possible value, which, as a side-effect, will update the FStates variable.

I wasn't sure, but that seems like a lot of additional calls one wouldn't expect when reading FStates in other situations. That's why I left it as is. Would like to hear your take on this.

Juha Manninen

2021-03-14 10:37

developer   ~0129648

> Would like to hear your take on this.

It looks like a hack and should be fixed IMO. If you come up with an elegant and intuitive solution, good.
Otherwise I can also study the code more later but not now ...

Remko Plantenga

2021-03-15 23:12

reporter   ~0129700

First idea:
1) Make FStates private again
2) Add a TListItem.GetStates function, which will return all states by calling TWSCustomListViewClass.ItemGetStates(). In the default implementation this will simply loop all states and call TWSCustomListViewClass.ItemGetState (which is what the current code does)
3) For Win32 the default implementation can be overriden and implemented by simply calling ListView_GetItemState with an appropriate mask
Sounds most reasonable, for now

Second idea:
1) Make FStates private again
2) Expand TListe.GetState and TWSCustomListViewClass.ItemGetState to accept a list of values, instead of a single value
Probably requires a lot of code changes in all interfaces :/

Third idea:
1) Keep States property
2) Create getter for FStates which will read all states by simply looping over the list of states and calling TWSCustomListViewClass.ItemGetState()
Like I mentioned: will create calls to other methods when retrieving States property :/

Your thoughts?

Juha Manninen

2021-03-17 11:30

developer   ~0129737

I would say, please use your own judgement.
I don't know the ListView code well. Detailed questions should be asked in mailing list which reaches more people.
However you seem to have a clear idea of the code and don't need to ask detailed questions.

Remko Plantenga

2021-03-20 22:25

reporter   ~0129794

Yeah, I was overthinking this. I chose option 1) and please find provided patch for r64845.

I used the same trick as TListItem.GetState, to be able to provide the default implementation which requires access to the private property FStates, which TWSCustomListViewClass does not have.
mypatch-2.diff (11,890 bytes)   
Index: docs/xml/lcl/comctrls.xml
===================================================================
--- docs/xml/lcl/comctrls.xml	(revision 64845)
+++ docs/xml/lcl/comctrls.xml	(working copy)
@@ -23516,6 +23435,8 @@
         <seealso/>
       </element>
 
+    <element name="TListItem.GetStates"><short>Returns all the states for this list item</short>
+      </element>
     </module>
     <!-- ComCtrls -->
   </package>
Index: lcl/comctrls.pp
===================================================================
--- lcl/comctrls.pp	(revision 64845)
+++ lcl/comctrls.pp	(working copy)
@@ -1054,6 +1054,7 @@
     function DisplayRect(Code: TDisplayCode): TRect;
     function DisplayRectSubItem(subItem: integer;Code: TDisplayCode): TRect;
     function EditCaption: Boolean;
+    function GetStates: TListItemStates;
 
     property Caption : String read GetCaption write SetCaption;
     property Checked : Boolean read GetChecked write SetChecked;
Index: lcl/include/customlistview.inc
===================================================================
--- lcl/include/customlistview.inc	(revision 64845)
+++ lcl/include/customlistview.inc	(working copy)
@@ -1345,57 +1345,10 @@
 
 function TCustomListView.GetNextItem(StartItem: TListItem;
   Direction: TSearchDirection; States: TListItemStates): TListItem;
-var
-  ACount: Integer;
-  StartIndex, AIndex: Integer;
-
-  {TODO: create public property States which reads states of item}
-  function GetItemStatesInternal(AItem: TListItem): TListItemStates;
-  begin
-    Result := [];
-    if AItem.GetState(Ord(lisCut)) then ;
-    if AItem.GetState(Ord(lisDropTarget)) then ;
-    if AItem.GetState(Ord(lisSelected)) then ;
-    if AItem.GetState(Ord(lisFocused)) then ;
-    Result := AItem.FStates;
-  end;
-
 begin
   Result := nil;
-  if StartItem = nil then
-    Exit;
-  StartIndex := StartItem.Index;
-  AIndex := StartIndex;
-  ACount := Items.Count;
-  case Direction of
-    sdAbove:
-      while AIndex>0 do
-      begin
-        dec(AIndex);
-        if States <= GetItemStatesInternal(Items[AIndex]) then
-          Exit(Items[AIndex]);
-      end;
-    sdBelow:
-      while AIndex < ACount-1 do
-      begin
-        inc(AIndex);
-        if States <= GetItemStatesInternal(Items[AIndex]) then
-          Exit(Items[AIndex]);
-      end;
-    sdAll:
-      while True do
-      begin
-        inc(AIndex);
-        Assert(AIndex <> StartIndex, 'TCustomListView.GetNextItem: AIndex=StartIndex');
-        if AIndex >= ACount then
-          Exit;
-{       begin           Do not wrap around. Will never return Nil. Issue #38565.
-          AIndex := -1;  continue;
-        end;  }
-        if States <= GetItemStatesInternal(Items[AIndex]) then
-          Exit(Items[AIndex]);
-      end;
-  end;
+  if HandleAllocated then
+     Result := TWSCustomListViewClass(WidgetSetClass).GetNextItem(Self, StartItem, Direction, States);
 end;
 
 procedure TCustomListView.ClearSelection;
Index: lcl/include/listitem.inc
===================================================================
--- lcl/include/listitem.inc	(revision 64845)
+++ lcl/include/listitem.inc	(working copy)
@@ -561,6 +561,30 @@
   else Result := AState in FStates;
 end;
 
+{------------------------------------------------------------------------------}
+{   TListItem GetStates                                                         }
+{------------------------------------------------------------------------------}
+function TListItem.GetStates: TListItemStates;
+var
+  States: TListItemStates;
+  LV: TCustomListView;
+begin
+  LV := FOwner.FOwner;
+
+  if TWSCustomListViewClass(LV.WidgetSetClass).ItemGetStates(LV, GetIndex, States)
+  then
+    Result := States
+  else
+  begin
+    Result := [];
+    if GetState(Ord(lisCut)) then ;
+    if GetState(Ord(lisDropTarget)) then ;
+    if GetState(Ord(lisSelected)) then ;
+    if GetState(Ord(lisFocused)) then ;
+    Result := FStates;
+  end;
+end;
+
 function TListItem.GetLeft: Integer;
 begin
   Result := Position.X;
Index: lcl/interfaces/win32/win32wscomctrls.pp
===================================================================
--- lcl/interfaces/win32/win32wscomctrls.pp	(revision 64845)
+++ lcl/interfaces/win32/win32wscomctrls.pp	(working copy)
@@ -147,6 +147,7 @@
     class function  ItemGetChecked(const ALV: TCustomListView; const AIndex: Integer; const AItem: TListItem): Boolean; override;
     class function  ItemGetPosition(const ALV: TCustomListView; const AIndex: Integer): TPoint; override;
     class function  ItemGetState(const ALV: TCustomListView; const AIndex: Integer; const AItem: TListItem; const AState: TListItemState; out AIsSet: Boolean): Boolean; override; // returns True if supported
+    class function  ItemGetStates(const ALV: TCustomListView; const AIndex: Integer; out AStates: TListItemStates): Boolean; override;
     class procedure ItemInsert(const ALV: TCustomListView; const AIndex: Integer; const AItem: TListItem); override;
     class procedure ItemSetChecked(const ALV: TCustomListView; const AIndex: Integer; const AItem: TListItem; const AChecked: Boolean); override;
     class procedure ItemSetImage(const ALV: TCustomListView; const AIndex: Integer; const AItem: TListItem; const ASubIndex, AImageIndex: Integer); override;
@@ -173,6 +174,7 @@
     class function GetTopItem(const ALV: TCustomListView): Integer; override;
     class function GetViewOrigin(const ALV: TCustomListView): TPoint; override;
     class function GetVisibleRowCount(const ALV: TCustomListView): Integer; override;
+    class function GetNextItem(const ALV: TCustomListView; const StartItem: TListItem; const Direction: TSearchDirection; const States: TListItemStates): TListItem; override;
 
     class procedure SelectAll(const ALV: TCustomListView; const AIsSet: Boolean); override;
     class procedure SetAllocBy(const ALV: TCustomListView; const AValue: Integer); override;
Index: lcl/interfaces/win32/win32wscustomlistview.inc
===================================================================
--- lcl/interfaces/win32/win32wscustomlistview.inc	(revision 64845)
+++ lcl/interfaces/win32/win32wscustomlistview.inc	(working copy)
@@ -650,6 +650,31 @@
   Result := True;
 end;
 
+class function TWin32WSCustomListView.ItemGetStates(const ALV: TCustomListView; const AIndex: Integer; out AStates: TListItemStates): Boolean;
+const
+  MASK = LVIS_CUT or LVIS_DROPHILITED or LVIS_FOCUSED or LVIS_SELECTED;
+var
+  Flags: Integer;
+begin
+  Result := False;
+
+  if not WSCheckHandleAllocated(ALV, 'ItemGetStates')
+  then Exit;
+
+  AStates := [];
+  Flags := ListView_GetItemState(ALV.Handle, AIndex, MASK);
+  if (Flags and LVIS_CUT) <> 0 then
+    Include(AStates, lisCut);
+  if (Flags and LVIS_DROPHILITED) <> 0 then
+    Include(AStates, lisDropTarget);
+  if (Flags and LVIS_FOCUSED) <> 0 then
+    Include(AStates, lisFocused);
+  if (Flags and LVIS_SELECTED) <> 0 then
+    Include(AStates, lisSelected);
+
+  Result := True;
+end;
+
 class procedure TWin32WSCustomListView.ItemInsert(const ALV: TCustomListView; const AIndex: Integer; const AItem: TListItem);
 var
   lvi: TLvItem;
@@ -1435,4 +1460,32 @@
   Windows.InvalidateRect(AHandle, nil, true);
 end;
 
+class function TWin32WSCustomListView.GetNextItem(const ALV: TCustomListView; const StartItem: TListItem;
+  const Direction: TSearchDirection; const States: TListItemStates): TListItem;
+var
+  Flags, Index: Integer;
+begin
+  Result := nil;
 
+  if not WSCheckHandleAllocated(ALV, 'GetNextItem')
+  then Exit;
+
+  Flags := 0;
+  case Direction of
+    sdAbove: Flags := LVNI_ABOVE;
+    sdBelow: Flags := LVNI_BELOW;
+    sdLeft: Flags := LVNI_TOLEFT;
+    sdRight: Flags := LVNI_TORIGHT;
+    sdAll: Flags := LVNI_ALL;
+  end;
+
+  if StartItem <> nil then Index := StartItem.Index else Index := -1;
+
+  if lisCut in States then Flags := Flags or LVNI_CUT;
+  if lisDropTarget in States then Flags := Flags or LVNI_DROPHILITED;
+  if lisFocused in States then Flags := Flags or LVNI_FOCUSED;
+  if lisSelected in States then Flags := Flags or LVNI_SELECTED;
+
+  Index := ListView_GetNextItem(ALV.Handle, Index, Flags);
+  if Index <> -1 then Result := ALV.Items[Index];
+end;
Index: lcl/widgetset/wscomctrls.pp
===================================================================
--- lcl/widgetset/wscomctrls.pp	(revision 64845)
+++ lcl/widgetset/wscomctrls.pp	(working copy)
@@ -130,6 +130,7 @@
     class function  ItemGetChecked(const ALV: TCustomListView; const AIndex: Integer; const AItem: TListItem): Boolean; virtual;
     class function  ItemGetPosition(const ALV: TCustomListView; const AIndex: Integer): TPoint; virtual;
     class function  ItemGetState(const ALV: TCustomListView; const AIndex: Integer; const AItem: TListItem; const AState: TListItemState; out AIsSet: Boolean): Boolean; virtual; // returns True if supported
+    class function  ItemGetStates(const ALV: TCustomListView; const AIndex: Integer; out AStates: TListItemStates): Boolean; virtual; // returns True if supported
     class procedure ItemInsert(const ALV: TCustomListView; const AIndex: Integer; const AItem: TListItem); virtual;
     class procedure ItemSetChecked(const ALV: TCustomListView; const AIndex: Integer; const AItem: TListItem; const AChecked: Boolean); virtual;
     class procedure ItemSetImage(const ALV: TCustomListView; const AIndex: Integer; const AItem: TListItem; const ASubIndex, AImageIndex: Integer); virtual;
@@ -155,6 +156,7 @@
     class function GetTopItem(const ALV: TCustomListView): Integer; virtual;
     class function GetViewOrigin(const ALV: TCustomListView): TPoint; virtual;
     class function GetVisibleRowCount(const ALV: TCustomListView): Integer; virtual;
+    class function GetNextItem(const ALV: TCustomListView; const StartItem: TListItem; const Direction: TSearchDirection; const States: TListItemStates): TListItem; virtual;
 
     class procedure SelectAll(const ALV: TCustomListView; const AIsSet: Boolean); virtual;
     class procedure SetAllocBy(const ALV: TCustomListView; const AValue: Integer); virtual;
@@ -587,6 +589,12 @@
   AIsSet:=false;
 end;
 
+class function TWSCustomListView.ItemGetStates(const ALV: TCustomListView; const AIndex: Integer; out AStates: TListItemStates): Boolean;
+begin
+  // returns True if supported
+  Result := False;
+end;
+
 class procedure TWSCustomListView.ItemInsert(const ALV: TCustomListView;
   const AIndex: Integer; const AItem: TListItem);
 begin
@@ -783,6 +791,50 @@
 
 end;
 
+//Default implementation
+class function TWSCustomListView.GetNextItem(const ALV: TCustomListView;
+  const StartItem: TListItem; const Direction: TSearchDirection; const States: TListItemStates): TListItem;
+var
+  ACount: Integer;
+  StartIndex, AIndex: Integer;
+begin
+  Result := nil;
+  if StartItem = nil then
+    Exit;
+  StartIndex := StartItem.Index;
+  AIndex := StartIndex;
+  ACount := ALV.Items.Count;
+  case Direction of
+    sdAbove:
+      while AIndex>0 do
+      begin
+        dec(AIndex);
+        if States <= ALV.Items[AIndex].GetStates then
+          Exit(ALV.Items[AIndex]);
+      end;
+    sdBelow:
+      while AIndex < ACount-1 do
+      begin
+        inc(AIndex);
+        if States <= ALV.Items[AIndex].GetStates then
+          Exit(ALV.Items[AIndex]);
+      end;
+    sdAll:
+      while True do
+      begin
+        inc(AIndex);
+        Assert(AIndex <> StartIndex, 'TWSCustomListView.GetNextItem: AIndex=StartIndex');
+        if AIndex >= ACount then
+          Exit;
+{       begin           Do not wrap around. Will never return Nil. Issue #38565.
+          AIndex := -1;  continue;
+        end;  }
+        if States <= ALV.Items[AIndex].GetStates then
+          Exit(ALV.Items[AIndex]);
+      end;
+  end;
+end;
+
 { TWSProgressBar }
 
 class procedure TWSProgressBar.ApplyChanges(const AProgressBar: TCustomProgressBar);
mypatch-2.diff (11,890 bytes)   

Juha Manninen

2021-03-21 09:38

developer   ~0129802

Applied, thanks.
Looks good. I hope the other widgetsets get support for those functions as well by somebody.

I experimented with the TListItem.State typecasts and will write about it in lazarus mailing list.

Issue History

Date Modified Username Field Change
2021-03-01 22:11 Remko Plantenga New Issue
2021-03-01 22:11 Remko Plantenga File Added: GetNextItem.zip
2021-03-05 10:52 Juha Manninen Assigned To => Juha Manninen
2021-03-05 10:52 Juha Manninen Status new => assigned
2021-03-05 10:58 Juha Manninen Status assigned => feedback
2021-03-05 10:58 Juha Manninen LazTarget => -
2021-03-05 10:58 Juha Manninen Note Added: 0129395
2021-03-07 14:55 Remko Plantenga Note Added: 0129479
2021-03-07 14:55 Remko Plantenga Status feedback => assigned
2021-03-07 16:37 Juha Manninen Note Added: 0129488
2021-03-11 22:38 Remko Plantenga Note Added: 0129585
2021-03-11 22:38 Remko Plantenga File Added: mypatch.diff
2021-03-12 11:27 Juha Manninen Note Added: 0129601
2021-03-12 11:28 Juha Manninen Status assigned => feedback
2021-03-12 21:49 Remko Plantenga Note Added: 0129615
2021-03-12 21:49 Remko Plantenga Status feedback => assigned
2021-03-14 10:37 Juha Manninen Note Added: 0129648
2021-03-15 23:12 Remko Plantenga Note Added: 0129700
2021-03-17 11:30 Juha Manninen Note Added: 0129737
2021-03-20 22:25 Remko Plantenga Note Added: 0129794
2021-03-20 22:25 Remko Plantenga File Added: mypatch-2.diff
2021-03-21 09:38 Juha Manninen Status assigned => resolved
2021-03-21 09:38 Juha Manninen Resolution open => fixed
2021-03-21 09:38 Juha Manninen Fixed in Revision => r64746, r64848
2021-03-21 09:38 Juha Manninen Widgetset Win32/Win64 => Win32/Win64
2021-03-21 09:38 Juha Manninen Note Added: 0129802