2007. szeptember 27., csütörtök

Reorder a TPageControl at runtime


Problem/Question/Abstract:

Let's say we have a TPageControl with 15 pages, the design time PageIndex of each page is 0 to 14 respectively. Now we want to change this order at runtime. The new order is saved in an *.ini file, the values for the new order is stored in an array of integer we will call MyOrder. Here is what the code would look like to change the order:

var
  i: Integer;

  for i := 0 to PageControl1.PageCount - 1 do
    PageControl1.Pages[i].PageIndex = MyOrder[i];

After executing this block of code the order of the pages is unchanged. Can you tell my why this doesn't work?

Answer:

Solve 1:

Try this chunk of code. The key is that you set the 0th page index, then the 1rst, then the 2nd and so on. Since you're doing them in order, they don't get screwed up by later assignments.

procedure TForm1.ReorderBtnClick(Sender: TObject);
const
  NewOrder: array[0..6] of Integer = (2, 0, 6, 1, 5, 3, 4);
var
  X: Integer;
  OrigPages: array of TTabSheet;
begin
  {Keep an ordered list of tab sheets}
  SetLength(OrigPages, 7);
  for X := 0 to 6 do
    OrigPages[X] := PageControl.Pages[X];
  {Reorder tab sheets}
  for X := 0 to 6 do
    OrigPages[NewOrder[X]].PageIndex := X;
  {Release ordered list of tab sheets}
  OrigPages := nil;
end;


Solve 2:

This won't make order of the pages unchanged, but it won't give you the order you expect either. Remember that while you are re-ordering the pagecontrol, the pagecontrol also reorders itself. For example, you have a pagecontrol with 5 tabs, labelled One, Two, Three, Four and Five. If you try to reorder so that these pages move to the positions 3, 5, 1, 4, 2 respectively, then the order your code will put them in will be Four, Three, Two, One, Five because of the way they get reordered by the pagecontrol while you are moving them around.

Now, the really cool thing, and what seems to be happening to you, is that if you apply the exact same reordering (using your algorithm) as first used, the tabs will go back to the positions that they started from! Step through your code, and I'm sure your reordering routine will be called twice. To reorder them successfully, you would simply use a search and position algorithm, working from the last tab position back to the first to get around the pagecontrol's own re-ordering. I'm not sure from your post whether your array of positions is "for this position, this is the page that goes here" or "for this page, this is the position it should be in", so here is how to re-order your pagecontrol. We'll use the Tag property to let the page remember where it should be. Use only *one* of the first two commented for-loops, depending on your data structure.

{ ... }
var
  i, j: Integer;
begin
  {Use this loop if the following describes your array:
  "For this position (index), this is the page that goes here (contents)"
  (that is, the contents of the array is the current pageindex, and the array
  index is the new pageindex for that page)}

  for i := 0 to PageControl1.PageCount - 1 do
    PageControl1.Pages[MyOrder[i]].Tag := i;

  {OR use this loop if the following describes your array:
  "For this page (index), this is the position it should be in (contents)"
  (That is, the array index corresponds to the current pageindex, and the contents
  of the array is the new pageindex that page should have)}

  for i := 0 to PageControl1.PageCount - 1 do
    PageControl1.Pages[i].Tag := MyOrder[i];

  {Then simply reorder the pagecontrol}
  for i := PageControl1.PageCount - 1 downto 0 do
    for j := 0 to i do
      if PageControl1.Pages[j].Tag = i then
      begin
        PageControl1.Pages[j].PageIndex := i;
        Break;
      end;
end;

One of the two will work for you.


Solve 3:

The correct method to disconnect tabsheets and reconnect them:

{ ... }
var
  I: Integer;
  L: TList;
begin
  L := TList.Create;
  try
    {This just disconnects - you should replace this with your code to disconnect
                them in the order you want to reconnect them}
    for I := PageControl1.PageCount - 1 downto 0 do
    begin
      L.Add(PageControl1.Pages[I]);
      PageControl1.Pages[I].PageControl := nil;
    end;
    {Reconnect...}
    for I := 0 to L.Count - 1 do
      TTabSheet(L[I]).PageControl := PageControl1;
  finally
    L.Free;
  end;
end;

Nincsenek megjegyzések:

Megjegyzés küldése