Повторный заказ 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, но когда я это сделал, я получил ошибку: Access Violation и 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 для поддержания порядка?
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;
Я думаю, что я нашел свой ответ, используя временный 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;