【问题标题】:How do I reorder items in a TCollection?如何重新排序 TCollection 中的项目?
【发布时间】:2012-01-08 04:20:43
【问题描述】:

我正在尝试实现 MoveItemUp 和 MoveItemDown 方法,它们在 TCollection 中将选定的行向上或向下移动一个索引。

添加到我的 TCollection 子类的以下代码不起作用:

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;

我相当肯定这在运行时必须是可能的,因为它在设计时由 Delphi IDE 本身完成,它提供了一种对列表中的集合项重新排序的方法。我希望通过简单地重新排序现有对象来做到这一点,而无需创建、销毁或分配任何对象。这可能来自 Classes.pas TCollection 的子类吗? (如果没有,我可能必须从源克隆制作自己的 TCollection)

【问题讨论】:

  • 设置集合项的Index属性应该做Item.Index:=Item.Index+1(这会调用集合项列表的Move)。如果需要任何特殊处理,SetIndex 方法将被覆盖。

标签: delphi tcollection tcollectionitem


【解决方案1】:

这是一个按 DisplayName 排序的类帮助器解决方案:您可以根据需要改进排序,我使用 TStringList 为我进行排序。类帮助器在您引用包含类帮助器的单元的任何地方都可用,因此如果您有实用程序单元,请将其放在那里。

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;

那么要使用该方法,只需假装它是 TCollection 类的方法。这也适用于 TCollection 的任何子类。

MyCollection.SortByDisplayNameMyCollectionItem.Collection.SortByDisplayName

【讨论】:

  • Embarcadero 最终解决了只能有一个类助手在范围内的问题吗?如果没有,那么这仅在 TCollection 范围内没有现有帮助程序时才有效,并且如果将其他一些帮助程序引入范围,则将来会中断。
  • @Deltics,类助手的范围没有改变。可以对助手进行子类化,但是您必须控制整个链。其他可能性是像 Ken 节目一样进行黑客攻击,并制作一个全局程序来对集合执行此操作或为类/记录中的集合收集更多“帮助”程序。
  • 为什么不直接for i:=0 to Self.Count -1 do SL.AddObject(Items[i].DisplayName), Items[i]); SL.Sort; for i:=0 to SL.Count -1 do TCollectionItem(SL.Objects[i]).Index := i;
【解决方案2】:

您可以使用类似的方法 - 为集合声明一个虚拟类类型,并使用它来访问该集合的内部 FItems,即 TList。然后,您可以使用TList.Exchange 方法来处理实际移动(当然也可以使用TList 的任何其他功能)。

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;

【讨论】:

  • 哦,这与其说是子类化,不如说是使用我自己希望正确排序的类来破解私有成员?
  • 是的。这就是为什么我提到它是一种黑客行为。在尝试对集合的项目进行排序时,我在使用 Index 时遇到了问题,并且没有实现交换。
  • 通过分配 TcollectionItem.Index 将项目向上/向下移动一个位置(我正在尝试这样做)似乎可以正常工作。
  • 是的,但如果你想做更多的事情,它不会,而且早期版本的 Delphi 在多次更改索引方面存在问题(IIRC,D2007 做过,但我会必须检查-甚至可能更早)。不过没关系。如果您需要移动的不仅仅是单个项目,这是一种替代方法。
【解决方案3】:

根据 VCL 源,您无需手动执行此操作。只需像@Sertac 建议的那样设置Index 属性,它应该可以正常工作。如果有源码,请查看TCollectionItem.SetIndex的代码。

【讨论】:

  • 感谢 Sertac。此答案实质上是复制您的想法,但已作为答案发布。\
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多