Bewegen von Steuerelementen in einem Gridpanel mit Delphi
Frage
In einer früheren Frage hier habe ich nach Drag n Drop im Gitterpanel gestellt.
Ziehen Sie n Drop -Steuerelemente in einem Gridpanel
Die Frage, die ich als nächstes habe, ist, dass ich seltsames Verhalten habe, wenn ich versuche, die Steuerung diagonal zu bewegen, wenn sie in der Nähe anderer Kontrollen sind. Kontrollen, die sich nicht bewegen, wechseln die Zellen. Auf und ab, seitlich ist es in Ordnung. Aber diagonale Bewegungen bewegt sich, wenn sich der bewegte Zellinhalt in derselben Zeile/Spalte mit anderen Zellen befindet, die Kontrollpersonen zu unerwarteten Verschiebungen verursachen. Ich habe versucht, die Verschiebungen immer noch zu beginnen. Es gibt eine Schließfunktion für das Gitterpanel, aber alles sperren. Es passiert, wenn sich der Tropfen auf einer leeren Zelle befindet, und sogar Zellen, die bereits Inhalte haben.
Hier ist das Testprojekt (Delphi 2010 mit 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;
Lösung
Hier geht es nicht um das Ziehen, wenn die Spalte und die Zeile eines Elements die Änderung in zwei Schritten verändern. Mit Ihrem Code, zuerst die Spalte, dann die Zeile. Wenn in der Säulenänderung FI bereits eine andere Steuerung gibt, wird diese andere Kontrolle beiseite geschoben, auch wenn seine Zelle nicht die ultimative Position der Zielzelle der beweglichen Kontrolle ist.
Begin/endupdate funktioniert nicht, die Steuersammlung überprüft nie die Aktualisierungszahl. Was Sie tun können, ist, einen geschützten Hack zu verwenden, um auf die Steuerelemente zuzugreifen InternalSetLocation
Methode. Diese Methode hat einen Parameter "Movexisting", den Sie "False" übergeben können.
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;
Möglicherweise müssen Sie testen, ob die Zielzelle leer ist oder nicht, bevor Sie "InternalSetLocation" aufrufen, je nachdem, was Sie erwarten, die richtige Steuerbewegung zu sein.
Andere Tipps
Ich benutze eine ganz andere Möglichkeit, den Job zu erledigen ... Erstellen Sie eine ganze Einheit, um eine Methode hinzuzufügen ExtCtrls.TControlCollection
ohne Einheit zu berühren ExtCtrls
(Erster Hack) und eine solche Methode verwenden InternalSetLocation
(zweiter Hack). Ich erkläre auch beide Hacks in diesem Beitrag.
Dann muss ich nur eine solche Einheit zur Implementierung hinzufügen (vor der Gridpanel -Deklaration) und die von mir erstellte Methode aufrufen ... sehr einfach zu verwenden.
So mache ich es Schritt für Schritt:
- Ich füge eine solche Einheit hinzu, die ich für einen solchen Job im Projekt verärgert habe (Datei hinzufügen)
- Ich füge zu meiner TForm -Schnittstelle hinzu, die Abschnittseinheit verwendet (oder wo ich sie brauche)
- Ich verwende meine Methode
AddControlAtCell
Anstatt vonExtCtrls.TControlCollection.AddControl
Hier ist die Einheit, die ich für einen solchen Job erstellt hatte, speichern Sie sie als 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.
Ich hoffe, die Kommentare sind genug klar. Bitte lesen Sie sie, um zu verstehen, warum und wie ich es mache.
Wenn ich dann dem Gridpanel in einer bestimmten Zelle eine Kontrolle hinzufügen muss, mache ich den nächsten einfachen Anruf:
TheGridPanel.AddControlAtCell(TheControl,ACloumn,ARow); // Add it at desired cell without affecting other cells
Ein sehr, sehr grundlegendes Beispiel für das Hinzufügen einer neu erstellten Laufzeit in einer bestimmten Zelle könnte wie folgt sein:
// 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
Bitte beachten Sie, dass ich diese beiden Hacks verwende:
type THackControlItem
Lassen Sie mich auf die Methode zugreifenInternalSetLocation
.type TGridPanel=class(ExtCtrls.TGridPanel)
Lassen Sie mich eine Methode hinzufügenExtCtrls.TGridPanel
ohne auch auch nur zu berühren (auch keine Quelle vonExtCtrls
)
Wichtig: Beachten Sie auch, dass ich es erwähne, das Gerät zur Verwendung der Schnittstelle jedes Formulars hinzuzufügen, in dem Sie die Methode verwenden möchten AddControlAtCell
; Das gilt für normale Personen, fortgeschrittene Personen könnten auch eine andere Einheit erstellen usw. Das „Konzept“ besteht darin Putted zur Entwurfszeit in einem Formular ... es muss die Implementierung der Verwendung einer solchen Formulareinheit anwenden.
Hoffe das hilft jemand anderem.
Die folgende Lösung funktioniert ohne jegliche Art von Hacking.
Mein Code ist im C ++ - Builder, aber ich denke, er ist einfach für Delphi -Benutzer zu verstehen, da er nur auf VCL -Funktionen beruht. PS: Beachten Sie, dass ich TPanels anstelle von Tbuttons ziehe (eine sehr geringfügige Änderung).
void TfrmVCL::ButtonDragDrop(TObject *Sender, TObject *Source, int X, int Y)
{
TRect CurCellRect;
TRect DestCellRect;
int Col;
int Row;
int destCol; int destRow;
int srcIndex; int destIndex;
TPanel *SrcBtn;
TPanel *DestBtn;
SrcBtn = dynamic_cast<TPanel *>(Source);
if (SrcBtn)
{
int ColCount = GridPnl->ColumnCollection->Count ;
int RowCount = GridPnl->RowCollection->Count ;
// SOURCE
srcIndex = GridPnl->ControlCollection->IndexOf( SrcBtn );
// DESTINATION
// we get coordinates of the button I drag onto
DestBtn= dynamic_cast<TPanel *>(Sender);
if (!DestBtn) return;
destIndex = GridPnl->ControlCollection->IndexOf( DestBtn );
destCol = GridPnl->ControlCollection->Items[ destIndex ]->Column; // the column for the dragged button
destRow = GridPnl->ControlCollection->Items[ destIndex ]->Row;
DestCellRect = GridPnl->CellRect[ destCol ][ destRow ];
// Check all cells
for ( Col = 0 ; Col < ColCount ; Col++ )
{
for ( Row = 0 ; Row < RowCount ; Row++ )
{
// Get the bounding rect for this cell
CurCellRect = GridPnl->CellRect[ Col ][ Row ];
if (IntersectRect_ForReal(DestCellRect, CurCellRect))
{
GridPnl->ControlCollection->Items[srcIndex]->SetLocation(Col, Row, false);
return;
}
}
}
}
}