4 Stimmen

Bewegen von Steuerelementen in einem Gridpanel mit Delphi

In einer früheren Frage hier habe ich nach Drag & Drop innerhalb des Gridpanels gefragt.

Drag & Drop Steuerelemente in einem GridPanel

Die nächste Frage, die ich habe, ist, dass ich ein seltsames Verhalten habe, immer wenn ich versuche, Steuerelemente diagonal zu verschieben, wenn sie in der Nähe anderer Steuerelemente sind. Steuerelemente, die nicht verschoben werden sollen, verschieben sich Zellen. Auf und ab, seitwärts ist es in Ordnung. Aber diagonale Bewegungen, wenn die verschobenen Zellinhalte in derselben Zeile / Spalte mit anderen Zellen liegen, die Steuerelemente enthalten, werden unerwartete Verschiebungen verursachen. Ich habe versucht, beginupdate/endupdate die Verschiebungen passieren trotzdem. Es gibt eine LOCK-Funktion für das Gridpanel, die jedoch nichts sperrt. Es passiert, wenn der Drop auf einer leeren Zelle liegt, und sogar Zellen, die bereits Inhalte haben.

Hier ist das Testprojekt (Delphi 2010 ohne 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;

...

4voto

Sertac Akyuz Punkte 53381

Dabei geht es nicht um Ziehen, wenn sich sowohl die Spalte als auch die Zeile eines Elements ändern, erfolgt die Änderung in zwei Schritten. Mit Ihrem Code zuerst die Spalte, dann die Zeile. Wenn sich in der Spaltenänderung z.B. bereits eine andere Steuerung befindet, wird diese andere Steuerung beiseite geschoben, auch wenn ihre Zelle nicht der endgültige Standort der Zielzelle der bewegten Steuerung ist.

Begin/EndUpdate funktioniert nicht, die Steuerungssammlung überprüft nie die Update-Zählung. Was Sie tun können, ist, einen geschützten Hack zu verwenden, um auf die Methode InternalSetLocation des Steuerelements zuzugreifen. Diese Methode enthält einen 'MoveExisting'-Parameter, den Sie auf 'False' setzen 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" von Zelle %d:%d nach Zelle %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;

Sie müssen möglicherweise testen, ob die Zielzelle leer ist oder nicht, bevor Sie 'InternalSetLocation' aufrufen, abhängig davon, was Sie für die richtige Steuerungsbewegung erwarten.

1voto

z666z666z Punkte 11

Ich benutze einen ziemlich anderen Weg, um den Job zu erledigen ... Erstelle eine ganze Einheit nur, um eine Methode zu ExtCtrls.TControlCollection hinzuzufügen, ohne die Einheit ExtCtrls zu ändern (erster Hack) und mache eine solche Methode mit InternalSetLocation (zweiter Hack). Ich erkläre auch beide Hacks in diesem Beitrag.

Dann brauche ich nur diese Einheit zur Implementierungsverwendung hinzufügen (vor der Gridpanel-Deklaration) und die von mir erstellte Methode aufrufen... sehr einfach zu benutzen.

So mache ich es, Schritt für Schritt:

  1. Ich füge diese Einheit dem Projekt hinzu (Datei hinzufügen)
  2. Ich füge meiner TForm-Benutzeroberfläche die Verwendung dieser Einheit hinzu (oder dort, wo ich sie brauche)
  3. Ich benutze meine Methode AddControlAtCell anstelle von ExtCtrls.TControlCollection.AddControl

Hier ist die Einheit, die ich für diesen Job erstellt habe, speichern Sie sie als unitTGridPanel_WithAddControlAtCell:

unit unitTGridPanel_WithAddControlAtCell;

Schnittstelle

verwendet
    Controls
   ,ExtCtrls
   ;

typ TGridPanel=Klasse(ExtCtrls.TGridPanel)
   privat
   public
     Verfahren AddControlAtCell(AControl:TControl;AColumn:Integer;ARow:Integer); // Fügt Steuerelement in bestimmte Zelle ein, falls bereits ein Steuerelement vorhanden ist, wird es gelöscht
 end;

Implementierung

verwendet
    SysUtils
   ;

typ
    THackControlItem=Klasse(TControlItem); // Um internen Zugriff auf InternalSetLocation-Prozedur zu erhalten
verfahren TGridPanel.AddControlAtCell(AControl:TControl;AColumn:Integer;ARow:Integer);
var
   TheControlItem:TControlItem; // Damit es in einer bestimmten Zelle hinzugefügt werden kann, da ExtCtrls.TControlCollection.AddControl mehrere BUGs enthält
begin // Steuerelement in bestimmte Zelle einfügen, falls bereits ein Steuerelement vorhanden ist, wird es gelöscht
     wenn   (-1ControlCollection.ControlItems[AColumn,ARow]) // Überprüfen, ob bereits Steuerelemente vorhanden sind
                 und // Ein Steuerelement befindet sich bereits in der Zelle
                    (Nicht<>ControlCollection.ControlItems[AColumn,ARow].Control) // Überprüfen, ob Zelle ein Steuerelement hat
               dann begin // Es gibt bereits ein Steuerelement, das gelöscht werden muss
                         ControlCollection.Delete(ControlCollection.IndexOf(ControlCollection.ControlItems[AColumn,ARow].Control)); // Löschen des Steuerelements
                    ende;
               TheControlItem:=ControlCollection.Add; // TControlItem erstellen
               TheControlItem.Control:=TControl(AControl); // Das Steuerelement in die spezifizierte Zelle setzen, ohne eine andere Zelle zu ändern
               THackControlItem(ControlCollection.Items[ControlCollection.IndexOf(AControl)]).InternalSetLocation(AColumn,ARow,False,False); // Das ControlItem in die Zelle setzen, ohne eine andere Zelle zu ändern
          ende
     sonst begin // Zelle ist außerhalb des Bereichs
               raise Exception.CreateFmt('Zelle [%d,%d] außerhalb des Bereichs in ''%s''.',[AColumn,ARow,Name]);
          ende;
end;

end.

Ich hoffe, die Kommentare sind ausreichend klar, bitte lesen Sie sie, um zu verstehen, warum und wie ich es mache.

Dann, wenn ich ein Steuerelement zu der Gridpanel an einer bestimmten Zelle hinzufügen muss, mache ich den nächsten einfachen Aufruf:

TheGridPanel.AddControlAtCell(TheControl,ACloumn,ARow); // Fügen Sie es an der gewünschten Zelle hinzu, ohne andere Zellen zu beeinflussen

Ein sehr, sehr einfaches Beispiel zum Hinzufügen eines zur Laufzeit neu erstellten TCheckBox an einer bestimmten Zelle könnte so aussehen:

// AColumn      ist vom Typ Integer
// ARow         ist vom Typ Integer
// ACheckBox    ist vom Typ TCheckBox
// TheGridPanel ist vom Typ TGridPanel
ACheckBox:=TCheckBox.Create(TheGridPanel); // Erstellen des Steuerelements, das hinzugefügt werden soll (ein Kontrollkästchen)
ACheckBox.Visible:=False; // Setzen Sie es vorerst auf unsichtbar (Optimierung auf Geschwindigkeit usw.)
ACheckBox.Color:=TheGridPanel.Color; // Einfach, um denselben Hintergrund wie auf der Gridpanel zu verwenden
ACheckBox.Parent:=TheGridPanel; // Setzen Sie den Elternknoten des Steuerelements als die Gridpanel (obligatorisch)
TheGridPanel.AddControlAtCell(ElCheckBox,ACloumn,ARow); // Fügen Sie es an der gewünschten Zelle hinzu, ohne andere Zellen zu beeinflussen
ElCheckBox.Visible:=True; // Nun ist es hinzugefügt, machen Sie es sichtbar
ElCheckBox.Enabled:=True; // Und natürlich, stellen Sie sicher, dass es aktiviert ist, wenn benötigt

Bitte beachten Sie, dass ich diese beiden Hacks verwende:

  1. typ THackControlItem ermöglicht es mir, auf die Methode InternalSetLocation zuzugreifen.
  2. typ TGridPanel=Klasse(ExtCtrls.TGridPanel) ermöglicht es mir, eine Methode zu ExtCtrls.TGridPanel hinzuzufügen, ohne sie zu berühren (auch ohne Quellcode von ExtCtrls zu benötigen)

Wichtig: Beachten Sie auch, dass ich erwähne, dass es erforderlich ist, die Einheit zu den Verwendungen der Benutzeroberfläche jeder Form hinzuzufügen, in der Sie die Methode AddControlAtCell verwenden möchten; das ist für normale Personen, fortgeschrittene Personen könnten auch eine andere Einheit erstellen, usw... das 'Konzept' ist, die Einheit vor der Deklaration der GridPanel, wo Sie sie verwenden möchten, in die Verwendung zu setzen... Beispiel: wenn GridPanel zur Entwurfszeit auf einem Formular platziert wird... muss es zur Implementierungsverwendung dieses Formular-Einheit gehen.

Ich hoffe, dass dies jemand anderem hilft.

0voto

Server Overflow Punkte 19774

Die unten stehende Lösung funktioniert ohne irgendwelche Hacks.

Mein Code ist in C++ Builder, aber ich denke, dass er einfach zu verstehen ist für Delphi-Benutzer, weil er nur auf VCL-Funktionen basiert. PS: Beachten Sie, dass ich TPanels statt 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(Source);
  if (SrcBtn)
     {
     int ColCount = GridPnl->ColumnCollection->Count ;
     int RowCount = GridPnl->RowCollection->Count ;

     // QUELLE
     srcIndex = GridPnl->ControlCollection->IndexOf( SrcBtn );

     // ZIEL
     // Wir erhalten die Koordinaten des Buttons, auf den ich ziehe
     DestBtn= dynamic_cast(Sender);
     if (!DestBtn) return;
     destIndex    = GridPnl->ControlCollection->IndexOf( DestBtn );
     destCol      = GridPnl->ControlCollection->Items[ destIndex ]->Column;  // die Spalte für den gezogenen Button
     destRow      = GridPnl->ControlCollection->Items[ destIndex ]->Row;
     DestCellRect = GridPnl->CellRect[ destCol ][ destRow ];

     // Überprüfe alle Zellen
     for ( Col = 0 ; Col < ColCount ; Col++ )
        {
        for ( Row = 0 ; Row < RowCount ; Row++ )
           {
             // Hole das Begrenzungsrechteck für diese Zelle
             CurCellRect = GridPnl->CellRect[ Col ][ Row ];

             if (IntersectRect_ForReal(DestCellRect, CurCellRect))
                {
                GridPnl->ControlCollection->Items[srcIndex]->SetLocation(Col, Row, false);
                return;
                }
           }
        }
     }
}

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X