Переупорядочить TObjectList

Мне нужно изменить порядок TObjectList в соответствии с некоторыми правилами. Как я могу этого добиться?

Поэтому я динамически добавляю панели в ScrollBox. Когда я добавляю их, я также добавляю их в ObjectList в том порядке, в котором они добавляются во время выполнения, для будущего использования. Затем я могу реорганизовать панели в scrollBox путем перетаскивания. Я хочу, чтобы ObjectList отражал тот же порядок, который устанавливается во время выполнения путем перетаскивания.

Вот мой код:

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 порядок тот же, что и раньше...

Опять же, я хочу иметь возможность упорядочивать ObjectList в соответствии с порядком панелей из scrollBox. В процедуре повторного заказа я фактически получаю все панели в желаемом порядке. Мне просто нужно, чтобы они были в том же порядке в моем ObjectList.

Есть ли другой способ сделать это? Другое дело, что я создал новый класс, который будет содержать индекс рядом с TPanel и использовать его в ObjectList для поддержания порядка?


person user1137313    schedule 21.07.2015    source источник


Ответы (2)


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;
person Remy Lebeau    schedule 21.07.2015
comment
Ваше решение кажется интересным, и, прежде всего, оно не использует временный ObjectList. Я проверю его, чтобы увидеть, работает ли он, и вернусь к вам. - person user1137313; 22.07.2015
comment
Это потрясающе и так элегантно. Спасибо за ваш ответ. Работает как шарм, хотя я не понимаю, как работает PanelList.Sort()... Не могли бы вы уточнить, пожалуйста? Для потомков... - person user1137313; 22.07.2015
comment
Я принял ваш ответ, потому что он намного элегантнее моего и использует меньше ресурсов и строк кода. Спасибо еще раз - person user1137313; 22.07.2015
comment
@ user1137313: посмотрите документацию: Classes.TList.Sort с Classes.TListSortCompare или Classes.TList.SortList с Classes.TListSortCompareFunc. - person Remy Lebeau; 22.07.2015

Я думаю, что нашел свой ответ, используя временный 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;
person user1137313    schedule 21.07.2015