TObjectList 重新排序

TObjectList re-order

我需要根据一些规则重新排序 TObjectList。我怎样才能做到这一点?

所以我动态地将面板添加到 ScrollBox。 当我添加它们时,我还按照它们在运行时添加的顺序将它们添加到 ObjectList 中,以供将来使用。然后我可以通过 drag/drop 重新组织 scrollBox 中的面板。 我希望 ObjectList 反映 drag/drop.

在运行时设置的相同顺序

这是我的代码:

var
  MainForm: TMainForm;
  PanelList,PanelListTMP:TObjectList;

implementation
...

procedure TMainForm.FormCreate(Sender: TObject);
begin
  PanelList:=TObjectList.Create;
  PanelListTMP:=TObjectList.Create;
end;

procedure TMainForm.Button1Click(Sender: TObject);
begin
  AddPanel('0');
  AddPanel('1');
  AddPanel('2');
  AddPanel('3');
  AddPanel('4');
end;

procedure TMainForm.Addpanel(what:string);
var
  pan:TPanel;
  bv:TShape;
begin
  pan:=TPanel.Create(self);
  pan.Parent:=TheContainer;
  pan.Height:=50;
  pan.BevelOuter:=bvNone;
  pan.BorderStyle:=bsNone;
  pan.Ctl3D:=false;
  pan.Name:='LayerPan'+what;
  pan.Caption:=what;
  pan.Align:=alBottom;
  pan.OnMouseDown:=panMouseDown;
end;

procedure TMainForm.panMouseDown(Sender: TObject; Button: TMouseButton;Shift: TShiftState; X, Y: Integer);
var
  i:integer;
  idu:String;
  panui:TPanel;
begin
  panui:=Sender as TPanel;
  panui.ParentColor:=false;
  panui.BringToFront;
  // DRAG DROP STUFF
  ReleaseCapture;
  panui.Perform(wm_nclbuttondown,HTCAPTION,0);

  for i := 0 to MainForm.ComponentCount - 1 do
    begin
      if MainForm.Components[i] is TWinControl then
        if TWinControl(MainForm.Components[i]) is TPanel then
        if (TWinControl(MainForm.Components[i]) as TPanel).Parent=MainForm.TheContainer then
          begin
            (TWinControl(MainForm.Components[i]) as TPanel).Align:=alBottom;
          end;
    end;
  TheContainer.ScrollInView(panui);
  ReOrderPanels;
end;


Procedure TMainForm.ReOrderPanels;
begin

end;

我应该在 ReOrderPanels 程序中做什么? 我正在考虑将 ScrollBox 的面板从下到上馈入新的 TObjectList (PanelListTMP),清除 PanelList 并从 PanelListTMP 重新添加它们,但是当我这样做时,出现错误:访问冲突和 EInvalidPointer - 无效指针操作

所以这就是我的想法:

procedure TMainForm.ReOrderPanels;
var
  ctrl:TControl;
  pos:TPoint;
  pan:TPanel;
  bad:boolean;
  ord,i:integer;
begin
  memo2.Lines.Add('*** new order START');
  panelListTMP.Clear;
 // scroll top
  TheContainer.VertScrollBar.Position := 0;
  // scroll down
  TheContainer.VertScrollBar.Position := TheContainer.VertScrollBar.Range;
  // get panel
  Pos:=TheContainer.ClientOrigin;
  Pos.Y:=Pos.Y+TheContainer.Height-5;
  ctrl := FindVCLWindow(pos) ;
  if ctrl is TPanel then
    if TPanel(ctrl).Parent = TheContainer then
    begin
      pan:=(ctrl as TPanel);
      panelListTMP.Add(pan);
    end;

  ord:=1;
  bad:=false;
  repeat
   repeat
       Pos.Y:=pos.Y-1;
   until (FindVCLWindow(pos) is TPanel)and(FindVCLWindow(pos)<>pan);
   if (FindVCLWindow(pos) is TPanel)and(FindVCLWindow(pos).Name<>'LayerPan') then
   begin
       pan:=FindVCLWindow(pos) as TPanel;
       containeru.VertScrollBar.Position := 0;
       containeru.ScrollInView(pan);
       ord:=ord+1;
       panelListTMP.Add(pan);
   end
   else
     bad:=true;
  until bad=true;

  // and now I do the swap between the ObjectLists...
  panelList.Clear;
  for i:=0 to PanelListTMP.Count-1  do
    begin
      (PanelListTMP.Items[i] as TPanel).Parent:=containeru;
      panelList.Add(PanelListTMP.Items[i]);
    end;
end;

所以我假设因为 ObjectList 正在存储指向实际对象的指针,然后当我清除初始 ObjectList 时,实际对象被释放,所以第二个 ObjectList 包含一个不再可行的指针列表。 . 但是我怎样才能达到我想要的呢?

因此,在 ButtonClick 上,我得到一个 ObjectList,其中包含按以下顺序排列的面板:

PanelList[0] - Panel0
PanelList[1] - Panel1
PanelList[2] - Panel2
PanelList[3] - Panel3
PanelList[4] - Panel4

在我将面板拖放到 ScrollBox 中之后,我可以得到这样的顺序(在 ScrollBox 中)

Panel3
panel1
Panel4
Panel2
Panel0

但是在ObjectList中,顺序和之前一样...

同样,我希望能够根据 scrollBox 中面板的顺序对 ObjectList 进行排序。 在重新排序过程中,我实际上得到了所需顺序的所有面板。 我只需要在我的 ObjectList 中以相同的顺序排列它们。

还有其他方法吗?另外,我创建了一个新的 class,它将在 TPanel 旁边保存一个索引,并在 ObjectList 中使用它来维护顺序?

TObjectList 有一个 OwnsObjects 属性 默认为 True。确保将其设置为 False,因为您不希望列表自动释放属于表单的对象。

至于 TObjectList 的实际排序,请考虑使用其 Sort()SortList() 方法。在您根据需要在其容器内重新定位面板后,调用 Sort()SortList()。当排序迭代列表时,您提供的排序回调将一次获得两个对象指针。使用对象相对于彼此的当前位置来告诉列表它们应该以什么顺序出现。

尝试这样的事情:

var
  MainForm: TMainForm;
  PanelList: TObjectList;

implementation

...

procedure TMainForm.FormCreate(Sender: TObject);
begin
  PanelList := TObjectList.Create(False);
end;

procedure TMainForm.FormDestroy(Sender: TObject);
begin
  PanelList.Free;
end;

procedure TMainForm.Button1Click(Sender: TObject);
begin
  AddPanel('0');
  AddPanel('1');
  AddPanel('2');
  AddPanel('3');
  AddPanel('4');
end;

procedure TMainForm.Addpanel(what: string);
var
  pan: TPanel;
  bv: TShape;
begin
  pan := TPanel.Create(Self);
  try
    pan.Parent := TheContainer;
    pan.Height := 50;
    pan.BevelOuter := bvNone;
    pan.BorderStyle := bsNone;
    pan.Ctl3D := false;
    pan.Name := 'LayerPan'+what;
    pan.Caption := what;
    pan.Align := alBottom;
    pan.OnMouseDown := panMouseDown;
    PanelList.Add(pan);
  except
    pan.Free;
    raise;
  end;
end;

procedure TMainForm.panMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
  i: integer;
  idu: String;
  panui, pan: TPanel;
  tmpList: TObjectList;
begin
  panui := Sender as TPanel;
  panui.ParentColor := false;
  panui.BringToFront;

  // DRAG DROP STUFF
  ReleaseCapture;
  panui.Perform(WM_NCLBUTTONDOWN, HTCAPTION, 0);

  tmpList := TObjectList.Create(False);
  try
    for i := 0 to TheContainer.ControlCount - 1 do
    begin
      if TheContainer.Controls[i] is TPanel then
        tmpList.Add(TPanel(TheContainer.Controls[i]));
    end;
    for i := 0 to tmpList.Count - 1 do
      TPanel(tmpList[i]).Align := alBottom;
  finally
    tmpList.Free;
  end;

  TheContainer.ScrollInView(panui);
  ReOrderPanels;
end;

function SortPanels(Item1, Item2: Pointer): Integer;
begin
  Result := TPanel(Item2).Top - TPanel(Item1).Top;
end;

procedure TMainForm.ReOrderPanels;
begin
  PanelList.Sort(SortPanels);

  // Alternatively:
  {
  PanelList.SortList(
    function(Item1, Item2: Pointer): Integer;
    begin
      Result := TPanel(Item2).Top - TPanel(Item1).Top;
    end
  );
  }
end;

我想我使用临时的 ObjectList 和 Extract(Object) 找到了答案

我的代码似乎有效:

procedure TMainForm.ReOrderPanels;
var
  ctrl:TControl;
  pos:TPoint;
  pan,panx:TPanel;
  bad:boolean;
  ord,i:integer;
begin

    panelListTMP.Clear;
    panelList.OwnsObjects:=false;

 // scroll top
  TheContainer.VertScrollBar.Position := 0;
  // scroll down
  TheContainer.VertScrollBar.Position := TheContainer.VertScrollBar.Range;
  // get panel
  Pos:=TheContainer.ClientOrigin;
  Pos.Y:=Pos.Y+TheContainer.Height-5;
  ctrl := FindVCLWindow(pos) ;
  if ctrl is TPanel then
    if TPanel(ctrl).Parent = TheContainer then
    begin
      pan:=(ctrl as TPanel);
      panelListTMP.Add(PanelList.Extract(pan) as TPanel);
    end;

  ord:=1;
  bad:=false;
  repeat
   repeat
   Pos.Y:=pos.Y-1;
   until (FindVCLWindow(pos) is TPanel)and(FindVCLWindow(pos)<>pan);
   if (FindVCLWindow(pos) is TPanel)and(FindVCLWindow(pos).Name<>'LayerPan') then
   begin
       pan:=FindVCLWindow(pos) as TPanel;
       TheContainer.VertScrollBar.Position := 0;
       TheContainer.ScrollInView(pan);
       ord:=ord+1;
       panelListTMP.Add(PanelList.Extract(pan) as TPanel);
   end
   else
     bad:=true;
  until bad=true;
  panelList.Clear;
  panelListTMP.OwnsObjects:=false;

  i:=0;
  while (PanelListTMP.Count<>0) do
      panelList.Add(PanelListTMP.Extract(PanelListTMP.Items[i])  as TPanel);

  panelList.OwnsObjects:=true;
  panelListTmp.Clear;
end;