How do I reorder items in a TCollection?
-
22-10-2019 - |
Question
I am trying to implement MoveItemUp and MoveItemDown methods that move a selected row up or down one index within a TCollection
.
The following code added to my subclass of TCollection does not work:
procedure TMyCollection.MoveRowDown(index: Integer);
var
item:TCollectionItem;
begin
if index>=Count-1 then exit;
item := Self.Items[index];
Self.Delete(index); // whoops this destroys the item above.
Self.Insert(index+1);
Self.SetItem(index+1,item); // this actually does an assign from a destroyed object.
end;
I am fairly sure this must be possible at runtime, as its done in designtime by the Delphi IDE itself which provides a way to reorder Collection items in a list. I am hoping to do this by simply reordering existing objects, without creating, destroying, or Assigning any objects. Is this possible from a subclass of Classes.pas TCollection? (If not, I may have to make my own TCollection from a source clone)
Solution
According to the VCL source, you don't need to manually do that. Simply set the Index
property like @Sertac suggested and it should work just fine. If you have the source, check out the code of TCollectionItem.SetIndex
.
OTHER TIPS
You can use something like this - declare a dummy class type for a collection, and use it to gain access to the internal FItems
of that collection, which is a TList
. You can then use the TList.Exchange
method to handle the actual move (or any other functionality of the TList
, of course).
type
{$HINTS OFF}
TCollectionHack = class(TPersistent)
private
FItemClass: TCollectionItemClass;
FItems: TList;
end;
{$HINTS ON}
// In a method of your collection itself (eg., MoveItem or SwapItems or whatever)
var
TempList: TList;
begin
TempList := TCollectionHack(Self).FItems;
TempList.Exchange(Index1, Index2);
end;
Here is a class helper solution that sorts by DisplayName: You can improve the sort if you like, I used a TStringList to do my sorting for me. The Class helper is available anywhere you reference the unit containing the class helper, so if you have a utility unit put it there.
interface
TCollectionHelper = class helper for TCollection
public
procedure SortByDisplayName;
end;
Implementation
procedure TCollectionHelper.SortByDisplayName;
var i, Limit : integer;
SL: TStringList;
begin
SL:= TStringList.Create;
try
for i := self.Count-1 downto 0 do
SL.AddObject(Items[i].DisplayName, Pointer(Items[i].ID));
SL.Sort;
Limit := SL.Count-1;
for i := 0 to Limit do
self.FindItemID(Integer(SL.Objects[i])).Index := i;
finally
SL.Free;
end;
end;
Then to use the method simply pretend it is a method of the TCollection class. This works on any subclass of TCollection as well.
MyCollection.SortByDisplayName
or MyCollectionItem.Collection.SortByDisplayName
.