1

I like to change the order of rows in a TGridPanel by clicking a button. The Gridpanel rows are dymamically created and contain a Panel with a manually docked form. The form has its own edit components and a Label with the index of the current row. The best way would be like this:

  1. Mark the row by clicking it
  2. Click a button "up" or "down".
  3. The selected row moves up/down.
  4. The Index of the row changes in the docked form.

Here is my attempt:

__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
    GridPanel1->RowCollection->Clear();
}
//---------------------------------------------------------------------------

void __fastcall TForm1::BitBtn_StepAddClick(TObject *Sender)
{
    GridPanel1->RowCollection->BeginUpdate();

    if (GridPanel1->RowCollection->Count > 0)
        GridPanel1->Height = GridPanel1->Height + 156;

    TRowItem * RowItem = GridPanel1->RowCollection->Add();
    RowItem->SizeStyle = ssAbsolute;
    RowItem->Value     = 156;

    TPanel * Panel2 = new TPanel(this);
    Panel2->Parent = GridPanel1;
    Panel2->Color  = clWhite;
    Panel2->Name   = "Panel" + IntToStr(GridPanel1->RowCollection->Count) + "2";
    Panel2->Align  = alClient;
    Panel2->AlignWithMargins = false;

    TForm2 * Form2 = new TForm2(this, GridPanel1->RowCollection->Count);
    Form2->ManualDock(Panel2, Panel2, alClient);
    Form2->Show();

    BitBtn_StepDelete->Enabled = true;

    GridPanel1->RowCollection->EndUpdate();
}
//---------------------------------------------------------------------------

void __fastcall TForm1::BitBtn_StepDeleteClick(TObject *Sender)
{
    // at the moment this button deletes the last row in GridPanel. But it would be fine to select a row by clicking it and delete the selected one.
    // After deleting it, the titles of the manually docked forms should change
    GridPanel1->RowCollection->BeginUpdate();

    GridPanel1->ControlCollection->Delete(GridPanel1->ControlCollection->Count - 1);
    GridPanel1->RowCollection->Delete(GridPanel1->RowCollection->Count - 1);

    if (GridPanel1->RowCollection->Count > 0)
        GridPanel1->Height = GridPanel1->Height - 156;
    else
        BitBtn_StepDelete->Enabled = false;

    GridPanel1->RowCollection->EndUpdate();
}
//---------------------------------------------------------------------------

void __fastcall TForm1::BitBtn_StepInsertClick(TObject *Sender)
{
    // Select a row by clicking it and then add a new row before
}
//---------------------------------------------------------------------------

void __fastcall TForm1::BitBtn_StepUpClick(TObject *Sender)
{
    // Select a row by clicking it and move it up by this button click
}
//---------------------------------------------------------------------------

void __fastcall TForm1::BitBtn_StepDownClick(TObject *Sender)
{
    // Select a row by clicking it and move it down by this button click
}
//---------------------------------------------------------------------------

Does anyone have an idea?

Ken White
  • 123,280
  • 14
  • 225
  • 444
  • Please show your own attempt to solve your task, that is a [mre] as required by SO. Simplify though, if the docked forms are not needed to demonstrate the problem, leave them out and only populate the grid with e.g. panels. – Tom Brunberg Oct 18 '21 at 06:50
  • I added some code. Is it possible to share the hole project? I've created an example project. – Christian Lindenblatt Oct 18 '21 at 08:33
  • 1
    You can simply set the `Index` property of a TRowItem to move it to a new position. – Uwe Raabe Oct 18 '21 at 10:11
  • That sounds interesting. How can I get the object RowItem. TRowItem * RI = GridPanel1->RowCollection->Items[0]; but it doen't work – Christian Lindenblatt Oct 18 '21 at 11:11
  • The TRowCollection is derived from TCellCollection, so the Items property returns TCellItem, but you actually get a TRowItem. – Uwe Raabe Oct 18 '21 at 12:07
  • Now I tried this: GridPanel1->RowCollection->Items[0]->Index = 2; for (int i = 0; i < GridPanel1->RowCollection->Count; i++) { GridPanel1->UpdateControlsRow(i); } But then the row 0 contains, the content of row 1 and vise versa. row 2 still contains the old content. The gridpanel seems to be really tricky. – Christian Lindenblatt Oct 19 '21 at 12:29

1 Answers1

1

Here's a Delphi implementation of a MoveUp button click handler. It depends on a variable SelRowId: integer holding the currently selected row index, or -1 if none selected.

It doesn't move the rows, but rather the content (e.g. panel) from one cell to another.

procedure TForm39.MoveUpBtnClick(Sender: TObject);
var
  NewRowId: integer;
begin
  GridPanel1.RowCollection.BeginUpdate;

  if InRange(SelRowId, 1, GridPanel1.ControlCollection.Count-1) then
  begin
    NewRowId := SelRowId - 1;
    GridPanel1.ControlCollection.ControlItems[0,SelRowId].Row := NewRowId;
    SelRowId := NewRowId;
  end;

  GridPanel1.RowCollection.EndUpdate;
end;

The MoveDown procedure is otherwise similar, but the test of SelRowId is

if InRange(SelRowId, 0, GridPanel1.ControlCollection.Count-2)

and assignment of NewRowId is

`NewRowId := SelRowId + 1;`

SelRowId must be initialized to -1, and set to -1 whenever no panels are selected.

I have assumed in my test app that clicking a panel ...

// Clicking a panel
// - deselects the panel if it was previously selected
// - deselects any other previously selected panel in preparation to:
// - selects the panel if it was not previously selected

In my test application I get the selected panels row in two steps:

  1. Get the index of the panel via ControlCollection.IndexOf(panel)

  2. Get the row from ControlCollection[indx]

    var
      indx: integer;
      Panel: TPanel;

    Panel := Sender as TPanel;
    {...}
    indx := TGridPanel(Panel.Parent).ControlCollection.IndexOf(Panel);
    SelRowId := TGridPanel(Panel.Parent).ControlCollection[indx].Row;

The complete implementation of my test project is here:

implementation

{$R *.dfm}
uses Math;

procedure TForm39.FormCreate(Sender: TObject);
begin
  SelRowId := -1;
end;

procedure TForm39.MoveUpBtnClick(Sender: TObject);
var
  NewRowId: integer;
begin
  GridPanel1.RowCollection.BeginUpdate;

  if InRange(SelRowId, 1, GridPanel1.ControlCollection.Count-1) then
  begin
    NewRowId := SelRowId - 1;
    GridPanel1.ControlCollection.ControlItems[0,SelRowId].Row := NewRowId;
    SelRowId := NewRowId;
  end;

  GridPanel1.RowCollection.EndUpdate;
end;

procedure TForm39.MoveDnBtnClick(Sender: TObject);
var
  NewRowId: integer;
begin
  GridPanel1.RowCollection.BeginUpdate;

  if InRange(SelRowId, 0, GridPanel1.ControlCollection.Count-2) then
  begin
    NewRowId := SelRowId + 1;
    GridPanel1.ControlCollection.ControlItems[0,SelRowId].Row := NewRowId;
    SelRowId := NewRowId;
  end;

  GridPanel1.RowCollection.EndUpdate;
end;

procedure TForm39.AddStepBtnClick(Sender: TObject);
var
  NewRow: TRowItem;
  P: Tpanel;
begin
  GridPanel1.RowCollection.BeginUpdate;

  NewRow := GridPanel1.RowCollection.Add;
  GridPanel1.Height := GridPanel1.Height + 50;
  NewRow.SizeStyle := ssAbsolute;
  NewRow.Value := 50;

  P := Tpanel.Create(self);
  P.Caption := 'Panel '+IntToStr(GridPanel1.RowCollection.Count);
  P.Align := alClient;
  P.AlignWithMargins := False;
  P.Parent := GridPanel1;
  P.ParentBackground := False;
  P.ParentColor := False;
  P.Color := Random(256) shl 16 + Random(256) shl 8 + Random(256);
  P.OnClick := PanelClick;

  GridPanel1.RowCollection.EndUpdate;
end;

// Clicking a panel
// - selects the panel if it was not previously selected
// - deselects the panel if it was previously selected
// - deselects any other previously selected panel
procedure TForm39.PanelClick(Sender: TObject);
var
  indx: integer;
  Panel: TPanel;
begin
  Panel := Sender as TPanel;

  // previously selected panel
  if Assigned(SelPanel) then
    SelPanel.Color := SavedColor; // reset its color
  if SelPanel = Panel then        // a second click unselects
  begin
    SelPanel := nil;
    SelRowId := -1;
    Exit;
  end;

  SavedColor := Panel.Color;  // save newly selected panels color
  Panel.Color := clRed;       // set selected color
  SelPanel := Panel;          // set selected panel

  indx := TGridPanel(Panel.Parent).ControlCollection.IndexOf(Panel);
  SelRowId := TGridPanel(Panel.Parent).ControlCollection[indx].Row;
end;
Tom Brunberg
  • 20,312
  • 8
  • 37
  • 54
  • Thank you, that works fine! Now I try to select a control by clicking it. I still get the clicked control index. But how is it possible to get the current row index of this control. I read sth about CellIndexToCell but I can't reach it because it is protected. In the following article it is described for Delphi. I got these information from this post: [link]https://stackoverflow.com/questions/38977196/getting-column-index-of-a-clicked-control-in-tgridpanel – Christian Lindenblatt Oct 20 '21 at 08:00
  • 1
    Oh, yeah, I had already forgot that answer of mine. There I used the feature in Delphi, that if you redeclare the class locally, then you get access to protected members. I don't know if you can use that in C++Builder. But the solution I present here, doesn't depend on that feature. – Tom Brunberg Oct 20 '21 at 09:04
  • Very nice! That works fine even in C++ Builder! I added a function to delete rows and the next step will be the implementation of a button "Insert new step at position...". When all functions will are ok, i will post it for other C++-programmers. Best regards and many thanks! – Christian Lindenblatt Oct 20 '21 at 19:12
  • Glad I could help. Please consider "accepting" my answer by clicking the checkmark green, and optionally also upvoting. For more information see [What should I do when someone answers my question?](https://stackoverflow.com/help/someone-answers) – Tom Brunberg Oct 21 '21 at 08:10