Search code examples
delphi-xetobjectlist

TObjectList re-order


I need to re-order a TObjectList, according to some rules. How can I achieve this?

So I add panels to a ScrollBox dinamically. When I add them, I also add them to the ObjectList in the order that they are added at runtime, for future use. Then I can re-organize the panels in the scrollBox by drag/drop. I want the ObjectList to mirror the same order that is set at runtime by drag/drop.

Here is my code:

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;

What should I do in the ReOrderPanels procedure? I was thinking about feeding the panels of the ScrollBox from bottom to top into a new TObjectList (PanelListTMP), clear the PanelList and re-add them from the PanelListTMP, but when I do that, I get an error: Access Violation, and EInvalidPointer - Invalid Pointer Operation

So this is what I thought:

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;

So I assume that because the ObjectList is storing pointers to the actual objects, then when I clear the initial ObjectList, the actual objects are freed, so the second ObjectList contains a list of pointers that are no longer viable... But then how can I achieve what I want?

So on ButtonClick, I get a ObjectList that contains panels in the following order:

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

After I drag - drop panels inside the ScrollBox, I can end up with an order like this (in the ScrollBox)

Panel3
panel1
Panel4
Panel2
Panel0

But in the ObjectList, the order is the same as before...

Again, I want to be able to have the ObjectList ordered according to the order of the panels from the scrollBox. In the re-order procedure I actually get all the panels in the desired order. I just need to have them in the same order in my ObjectList.

Is there any other way of doing this? Other that with me creating a new class that would hold an index beside a TPanel and use that in the ObjectList to maintain the order?


Solution

  • TObjectList has an OwnsObjects property that is True by default. Make sure to set it to False since you don't want the list to auto-free the objects as they are owned by the Form.

    As for the actual sorting of the TObjectList, consider using its Sort() or SortList() method for that. After you have repositioned the Panels as desired within their container, call Sort() or SortList(). The sorting callback you provide will be given two object pointers at a time while the sorting is iterating the list. Use the current positions of the objects relative to each other to tell the list what order they should appear in.

    Try something like this:

    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;