Moving controls in a grid using Delphi

In the previous question here, I asked about dragging n in the grid.

Drag and Drop N Drop Controls into the GridPanel

The next question is that I have strange behavior when I try to move the controls diagonally when they are next to other controls. Controls that do not allow movement are shift cells. Up and down, sideways, all is well. But diagonal movements, when the moved contents of the cell are in the same row / column with the other cells that hold the controls, will cause unexpected shifts. I tried beginupdate / endupdate, shifts still occur. There is a LOCK function for the grid, but nothing to block. This happens when a drop is in an empty cell, and even cells that already have content.

here is a test project (Delphi 2010 without exe) http://www.mediafire.com/?xmrgm7ydhygfw2r

type TForm1 = class(TForm) GridPanel1: TGridPanel; btn1: TButton; btn3: TButton; btn2: TButton; lbl1: TLabel; procedure FormCreate(Sender: TObject); private { Private declarations } procedure GridPanelDragDrop(Sender, Source: TObject; X, Y: Integer); procedure btnDragOver(Sender, Source: TObject; X, Y: Integer; State: TDragState; var Accept: Boolean); procedure btnDragDrop(Sender, Source: TObject; X, Y: Integer); public { Public declarations } end; var Form1: TForm1; implementation {$R *.dfm} procedure SetColumnWidths(aGridPanel: TGridPanel); var i,pct: Integer; begin aGridPanel.ColumnCollection.BeginUpdate; pct:=Round(aGridPanel.ColumnCollection.Count/100); for i := 0 to aGridPanel.ColumnCollection.Count - 1 do begin aGridPanel.ColumnCollection[i].SizeStyle := ssPercent; aGridPanel.ColumnCollection[i].Value := pct; end; aGridPanel.ColumnCollection.EndUpdate; end; procedure SetRowWidths(aGridPanel: TGridPanel); var i,pct: Integer; begin aGridPanel.RowCollection.BeginUpdate; pct:=Round(aGridPanel.RowCollection.Count/100); for i := 0 to aGridPanel.RowCollection.Count - 1 do begin aGridPanel.RowCollection[i].SizeStyle := ssPercent; aGridPanel.RowCollection[i].Value := pct; end; aGridPanel.RowCollection.EndUpdate; end; procedure TForm1.FormCreate(Sender: TObject); begin btn1.OnDragOver := btnDragOver; btn2.OnDragOver := btnDragOver; btn3.OnDragOver := btnDragOver; GridPanel1.OnDragOver := btnDragOver; GridPanel1.OnDragDrop := GridPanelDragDrop; btn1.OnDragDrop := btnDragDrop; btn2.OnDragDrop := btnDragDrop; btn3.OnDragDrop := btnDragDrop; SetColumnWidths(GridPanel1); SetRowWidths(GridPanel1); end; procedure TForm1.btnDragOver(Sender, Source: TObject; X, Y: Integer; State: TDragState; var Accept: Boolean); begin Accept := (Source is TButton); end; procedure TForm1.btnDragDrop(Sender, Source: TObject; X, Y: Integer); var src_x,src_y, dest_x, dest_y: Integer; btnNameSrc,btnNameDest: string; src_ctrlindex,dest_ctrlindex:integer; begin if Source IS tBUTTON then begin //GridPanel1.ColumnCollection.BeginUpdate; btnNameSrc := (Source as TButton).Name; btnNameDest := (Sender as TButton).Name; src_ctrlindex := GridPanel1.ControlCollection.IndexOf(Source as tbutton); src_x := GridPanel1.ControlCollection.Items[src_ctrlindex].Column; src_y := GridPanel1.ControlCollection.Items[src_ctrlindex].Row; dest_ctrlindex := GridPanel1.ControlCollection.IndexOf(Sender as tbutton); dest_x := GridPanel1.ControlCollection.Items[dest_ctrlindex].Column; dest_y := GridPanel1.ControlCollection.Items[dest_ctrlindex].Row; GridPanel1.ControlCollection[src_ctrlindex].Column := dest_x; GridPanel1.ControlCollection[src_ctrlindex].Row := dest_y; //GridPanel1.ColumnCollection.EndUpdate; lbl1.Caption := Format('"%s" from cell %d:%d to Cell %s=%d:%d', [btnNameSrc,src_x,src_y,btnNameDest,dest_x,dest_y]); end; end; procedure TForm1.GridPanelDragDrop(Sender, Source: TObject; X, Y: Integer); var DropPoint: TPoint; CellRect: TRect; i_col, i_row, src_x,src_y, dest_x, dest_y: Integer; btnNameSrc,btnNameDest: string; src_ctrlindex:integer; begin if Source is tbutton then begin btnNameSrc := (Source as TButton).Name; btnNameDest := ''; src_ctrlindex := GridPanel1.ControlCollection.IndexOf(Source as tbutton); src_x := GridPanel1.ControlCollection.Items[src_ctrlindex].Column; src_y := GridPanel1.ControlCollection.Items[src_ctrlindex].Row; DropPoint := Point(X, Y); for i_col := 0 to GridPanel1.ColumnCollection.Count-1 do for i_row := 0 to GridPanel1.RowCollection.Count-1 do begin CellRect := GridPanel1.CellRect[i_col, i_row]; if PtInRect(CellRect, DropPoint) then begin // Button was dropped over Cell[i_col, i_row] dest_x := i_col; dest_y := i_row; Break; end; end; lbl1.Caption := Format('"%s" from cell %d:%d to Cell %s=%d:%d', [btnNameSrc,src_x,src_y,btnNameDest,dest_x,dest_y]); GridPanel1.ControlCollection[src_ctrlindex].Column := dest_x; GridPanel1.ControlCollection[src_ctrlindex].Row := dest_y; end; end; 
+4
source share
2 answers

It’s not about drag and drop, when both the column and the row change, the change occurs in two stages. With code, first a column, then a row. If fi is changed in the column, then there is already another control, this other control is thrown to the side, even if its cell is not the final location of the target cell of the moving control.

Begin / EndUpdate will not work; the management collection never checks the number of updates. What you can do is use a secure hack to access the InternalSetLocation control. This method has a parameter MoveExisting, which you can pass "False".

 type THackControlItem = class(TControlItem); procedure TForm1.GridPanelDragDrop(Sender, Source: TObject; X, Y: Integer); var [...] begin if Source is tbutton then begin [...] lbl1.Caption := Format('"%s" from cell %d:%d to Cell %s=%d:%d', [btnNameSrc,src_x,src_y,btnNameDest,dest_x,dest_y]); THackControlItem(GridPanel1.ControlCollection[src_ctrlindex]). InternalSetLocation(dest_x, dest_y, False, False); // GridPanel1.ControlCollection[src_ctrlindex].Column := dest_x; // GridPanel1.ControlCollection[src_ctrlindex].Row := dest_y; end; end; 

You may need to check if the target cell is empty or not before calling "InternalSetLocation" depending on what you expect from the right control.

+4
source

I use a completely different way to complete the task ... Create an integer unit to add the method to ExtCtrls.TControlCollection without touching the ExtCtrls unit (first hack), and use this method InternalSetLocation (second hack). I also explain both hacks in this post.

Then I just need to add such a block to the usage usage section (before the gridpanel declaration) and call the method I created ... very easy to use.

Here is how I do it, step by step:

  • I include a block that I masked for such a job in the project (add file)
  • I add TForm to my interface using a section such a block (or where I need it)
  • I use my AddControlAtCell method instead of ExtCtrls.TControlCollection.AddControl

Here is the block I created for such a job, save it as unitTGridPanel_WithAddControlAtCell :

 unit unitTGridPanel_WithAddControlAtCell; interface uses Controls ,ExtCtrls ; type TGridPanel=class(ExtCtrls.TGridPanel) private public procedure AddControlAtCell(AControl:TControl;AColumn:Integer;ARow:Integer); // Add Control on specifed cell, if there already exists a Control it will be deleted end; implementation uses SysUtils ; type THackControlItem=class(TControlItem); // To get internal access to InternalSetLocation procedure procedure TGridPanel.AddControlAtCell(AControl:TControl;AColumn:Integer;ARow:Integer); var TheControlItem:TControlItem; // To let it be added in a specified cell, since ExtCtrls.TControlCollection.AddControl contains multiply BUGs begin // Add Control on specifed cell, if there already exists a Control it will be deleted if (-1<AColumn)and(AColumn<ColumnCollection.Count) // Cell with valid Column and // Cell inside valid range (-1<ARow)and(ARow<RowCollection.Count) // Cell with valid Row then begin // Valid cell, must check if there is already a control if (Nil<>ControlCollection.ControlItems[AColumn,ARow]) // Check if there are any controls and // A control is already on the cell (Nil<>ControlCollection.ControlItems[AColumn,ARow].Control) // Check if cell has a control then begin // There is already a control, must be deleted ControlCollection.Delete(ControlCollection.IndexOf(ControlCollection.ControlItems[AColumn,ARow].Control)); // Delete the control end; TheControlItem:=ControlCollection.Add; // Create the TControlItem TheControlItem.Control:=TControl(AControl); // Put the Control in the specified cell without altering any other cell THackControlItem(ControlCollection.Items[ControlCollection.IndexOf(AControl)]).InternalSetLocation(AColumn,ARow,False,False); // Put the ControlItem in the cell without altering any other cell end else begin // Cell is out of range raise Exception.CreateFmt('Cell [%d,%d] out of range on ''%s''.',[AColumn,ARow,Name]); end; end; end. 

I hope the comments are clear enough, please read them to understand why and how I do it.

Then, when I need to add a control to the grid in the specified cell, I make the following simple call:

 TheGridPanel.AddControlAtCell(TheControl,ACloumn,ARow); // Add it at desired cell without affecting other cells 

A very simple example of adding a newly created TCheckBox in a specific cell might be:

 // AColumn is of Type Integer // ARow is of Type Integer // ACheckBox is of Type TCheckBox // TheGridPanel is of Type TGridPanel ACheckBox:=TCheckBox.Create(TheGridPanel); // Create the Control to be added (a CheckBox) ACheckBox.Visible:=False; // Set it to not visible, for now (optimization on speed, e tc) ACheckBox.Color:=TheGridPanel.Color; // Just to use same background as on the gridpanel ACheckBox.Parent:=TheGridPanel; // Set the parent of the control as the gridpanel (mandatory) TheGridPanel.AddControlAtCell(ElCheckBox,ACloumn,ARow); // Add it at desired cell without affecting other cells ElCheckBox.Visible:=True; // Now it is added, make it visible ElCheckBox.Enabled:=True; // And of course, ensure it is enabled if needed 

Note that I use these two hacks:

  • type THackControlItem Let me access the InternalSetLocation method.
  • type TGridPanel=class(ExtCtrls.TGridPanel) Let me add the ExtCtrls.TGridPanel method ExtCtrls.TGridPanel even touching (without needing an ExtCtrls source)

Important. Also note that I mention that it requires adding a module to the use of the interface of each form, where you want to use the AddControlAtCell method; that is, for normal people, advanced people can also create another unit, etc. The "concept" is for the unit to use before the GridPanel declaration where you use it ... example: if the GridPanel is exposed on the form during development ... it must continue to use such a form unit.

Hope this helps someone else.

+1
source

Source: https://habr.com/ru/post/1344468/


All Articles