Search code examples
delphigridpanel

Removing controls from a TGridPanel


I'm using TGridPanel to manage a number of panels. I create the panels and add them to the GridPanel using code like the following:

var
  pnl: TPanel;
begin
  pnl := TPanel.Create(GridPanel2);
  pnl.Caption := 'Panel One';
  pnl.Tag := 1;
  pnl.Parent := GridPanel2;
  pnl.Name := 'pnlOne';

  GridPanel2.ControlCollection.AddControl(pnl);


  pnl := TPanel.Create(GridPanel2);
  pnl.Caption := 'Panel Two';
  pnl.Tag := 2;
  pnl.Parent := GridPanel2;
  pnl.Name := 'pnlTwo';

  GridPanel2.ControlCollection.AddControl(pnl);


  pnl := TPanel.Create(GridPanel2);
  pnl.Caption := 'Panel Three';
  pnl.Tag := 3;
  pnl.Parent := GridPanel2;
  pnl.Name := 'pnlThree';

  GridPanel2.ControlCollection.AddControl(pnl);
end;

You will notice that each panel has a different tag value.

I'd like to remove a panel from the GridPanel based on the value in the panel's tag property. I have tried the following code:

var
  ii: integer ;
  pnl: TPanel;
begin
  for ii := 0 to GridPanel2.ControlCollection.Count -1 do begin
    if GridPanel2.ControlCollection[ii].Control.Tag = 1 then begin
      pnl := GridPanel2.ControlCollection[ii].Control as TPanel;

      GridPanel2.ControlCollection.RemoveControl(pnl);

      freeandnil(pnl);
    end;
  end;
  gridpanel2.Refresh();
end;

This works well providing the panel is the last panel in the collection. If I try to remove the panel with tag = 1 or tag = 2 I get an out of range error. Clicking "continue" in the debugger leaves a space where the removed panel was, so does remove the panel.

What I'd like to see is, say panel 2 removed and subsequent panels shuffled down one place to leave no gaps.

How do I do this?

I'm using Delphi 10.1 Berlin if that matters.


Solution

  • As always when deleting an item from a list or collection you need to take precaution when the count changes. A for loop count is determined at the beginning of the loop. Now, if you delete an item from the list you will hit a non-existing index when the for loop continues to the end.

    You can avoid this in many ways, f.ex. by breaking out of the loop, once you have found and deleted the item.

      freeandnil(pnl);
      break;
    

    Another way, is to run the for loop backwards

      for ii := GridPanel2.ControlCollection.Count -1 downto 0 do begin
    

    Or you can use Repeat Until or Whileloops which checks the condition to continue on every turn of the loop.

    To update the grid panel after deleting items call either or both of

      gridpanel2.UpdateControlsRow();
      gridPanel2.UpdateControlsColumn();
    

    However, it feels quite tricky to get the order right