使用Delphi在gridpanel中移动控件

时间:2011-03-20 15:29:57

标签: delphi gridpanel

在上一个问题中,我询问了网格面板中的拖放问题。

Drag N Drop controls in a GridPanel

我接下来的问题是,当我在接近其他控件时尝试对角移动控件时,我会遇到奇怪的行为。不假设移动的控件正在移动单元格。上下,侧身很好。但是对角线移动,当移动的单元格内容与同一行/列保持控制时,将导致意外的移位。我已经尝试过beginupdate / endupdate仍然会发生变化。网格面板有一个LOCK功能,但锁定任何东西。当丢弃在空单元格上,甚至是已经有内容的单元格时,就会发生这种情况。

这是测试项目(Delphi 2010 w / o 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;

3 个答案:

答案 0 :(得分:4)

这不是关于拖动,当一个项目的列和行都在改变时,改变发生在两个步骤中。使用您的代码,首先是列,然后是行。如果在列中更改f.i.,恰好已经存在其他控件,则将此其他控件推到一边,即使其单元格不是移动控件的目标单元格的最终位置。

Begin / EndUpdate不起作用,控件集合永远不会检查更新计数。您可以做的是使用受保护的hack来访问控件项的InternalSetLocation方法。此方法有一个'MoveExisting'参数,您可以传递'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;

在调用'InternalSetLocation'之前,您可能需要测试目标单元格是否为空,具体取决于您期望的正确控制移动。

答案 1 :(得分:1)

我使用一种完全不同的方式来完成工作...创建整个单元只是为了向ExtCtrls.TControlCollection添加一个方法而不触及单位ExtCtrls(第一次黑客攻击)并使这样的方法使用{{ 1}}(第二个黑客)。我也在这篇文章中解释了两个黑客。

然后我只需要将这个单元添加到实现使用部分(在gridpanel声明之前)并调用我创建的方法......非常简单易用。

以下是我如何做到的,一步一步:

  1. 我将这样的工作单元包含在项目中(添加文件)
  2. 我添加到我的TForm接口使用部分这样的单位(或我需要的地方)
  3. 我使用方法InternalSetLocation代替AddControlAtCell
  4. 以下是我为此类工作创建的单元,将其另存为ExtCtrls.TControlCollection.AddControl

    unitTGridPanel_WithAddControlAtCell

    我希望评论足够清楚,请阅读它们以了解我为何以及如何做到这一点。

    然后,当我需要将控件添加到指定单元格的gridpanel时,我会进行下一个简单的调用:

    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.
    

    在特定单元格中添加运行时新创建的TCheckBox的一个非常非常基本的示例可能是这样的:

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

    请注意我使用这两个黑客:

    1. // 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 让我访问方法type THackControlItem
    2. InternalSetLocation让我向[{1}}添加一个方法,甚至没有触及(既不需要type TGridPanel=class(ExtCtrls.TGridPanel)的来源)
    3. 重要提示:另请注意,我提到要将单位添加到您要使用方法ExtCtrls.TGridPanel的每个表单的界面的使用中;对于普通人来说,高级人员也可以创建另一个单元等...'概念'是在GridPanel声明之前将单元用于使用它的地方......例如:如果GridPanel是在设计时将表格放在表格上......它必须继续使用这种表格单位。

      希望这有助于其他人。

答案 2 :(得分:0)

下面的解决方案无需任何黑客手段即可使用。

我的代码在C ++ Builder中,但是我认为对于Delphi用户来说,这仅仅是理解,因为它仅依赖于VCL函数。 PS:请注意,我拖动TPanels而不是TButtons(这是非常小的更改)。

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;
                }
           }
        }
     }
}