3

I'm using a TGridPanel to hold some panels. At design time, I've set the grid panel to have 1 row and 5 columns.

I can add a panel to the grid using this code, which works well:

procedure TForm6.AddPanelToGrid(const ACaption: string);
var
  pnl: TPanel;
begin
  pnl := TPanel.Create(gpOne);
  pnl.Caption := ACaption;
  pnl.Parent := gpOne;
  pnl.Name := 'pnlName' + ACaption;
  pnl.OnClick := gpOne.OnClick;
  pnl.ParentBackground := false;
  pnl.ParentColor := false;
  pnl.Color := clLime;
  pnl.Font.Size := 14;
  gpOne.ControlCollection.AddControl(pnl);
  pnl.Height := pnl.Width;
end;

What I want to do is remove a TPanel from the grid when I click on it (which is why I have set the on click handler to that of the grid panel in the above code).

In that click handler I do this, which almost works:

procedure TForm6.gpOneClick(Sender: TObject);
begin
  if not (sender is TPanel) then exit;

  gpOne.ControlCollection.RemoveControl(Sender as TPanel);
  (Sender as TPanel).Free;

  gpOne.UpdateControlsColumn( 0 );  <<<-------
  gpOne.UpdateControlsRow(0);

  gpOne.Refresh();
end;

Using a parameter for UpdateControlColumn() causes the order of the panels in the grid to change - the first and second swap places.

I can get around this by adding the column idex to the panel's tag property, then pass that to UpdateControlColumn(). This then works, but once a panel has been removed the higher tag numbers are no longer valid - the panels have moved column.

So, how can I get the column that a panel is in from within the OnClick handler?

I'm using Delphi 10.1 Berlin - if that makes any difference.

To test this, I started a new project, added a TGridPanel, set it to have 1 row and 5 equally widthed columns. I added 6 TButton controls and created an OnClick handler for each with the following code:

AddPanelToGrid('One');  // changing the string for each button.

Click a few buttons to add some panels, then click the panels to remove them.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Michael Vincent
  • 1,620
  • 1
  • 20
  • 46
  • 5
    It is **never** safe to call `Free()` on the `Sender` of an event while you are still inside the event handler. The RTL still needs access to the object after the event handler exits. If you need to free the `Sender`, you *must* delay the free until after the handler has exited. Usually I do that by using `PostMessage()` to post myself a custom window message with the object pointer in the `LParam` and then let the message handler free the object when it is safe to do so. This is similar to how `TForm` uses `CM_RELEASE` to free itself when its `OnClose` event handler returns `Action=caFree` – Remy Lebeau Aug 16 '16 at 16:44
  • Thank you, @RemyLebeau, if I continue with this method I'll incorporate your suggestion. Kind regards, – Michael Vincent Aug 17 '16 at 07:37

1 Answers1

1

TCustomGridPanel has a pair of useful functions, CellIndexToCell() and CellToCellIndex, but they are not public and thus not directly accessible from a TGridPanel.

To make them available declare TGridPanel anew as below:

type
  TGridPanel = class(Vcl.ExtCtrls.TGridPanel)  // add this
  end;                                         // -"-
  TForm27 = class(TForm)
    Button1: TButton;
    gpOne: TGridPanel;
    ...
  end;

Then add rand c variables for row and col, add the call to CellIndexToCell() and use c as argument for UpdateControlsColumn:

procedure TForm27.gpOneClick(Sender: TObject);
var
  r, c: integer;
begin
  if not (sender is TPanel) then exit;

  gpOne.CellIndexToCell(gpOne.ControlCollection.IndexOf(Sender as TPanel), c, r); // add this

  gpOne.ControlCollection.RemoveControl(Sender as TPanel);
  (Sender as TPanel).Free;

  gpOne.UpdateControlsColumn( c );  // <<<-------
  gpOne.UpdateControlsRow(0);

  gpOne.Refresh();
end;

And follow advise of Remy Lebeau, regarding freeing the panel. ( I just noticed his comment).


If you haven't already, you may also want to take a look at TFlowPanel and its FlowStyle property. TflowPanel reordering after deletion is more predictable if you use more than one row, but depends of course on what you need.

Tom Brunberg
  • 20,312
  • 8
  • 37
  • 54
  • Thank you, @TomBrunberg - much appreciated. All I need is a single row of up to 5 panels - no flowing or resizing is necessary, so TFlowPanel would be over kill. Kind regards, – Michael Vincent Aug 17 '16 at 07:44