【问题标题】:Moving controls in a gridpanel with Delphi使用 Delphi 在网格面板中移动控件
【发布时间】:2011-07-19 04:07:25
【问题描述】:

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

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;

【问题讨论】:

    标签: delphi gridpanel


    【解决方案1】:

    这与拖动无关,当一个项目的列和行都在更改时,更改分两步进行。使用您的代码,首先是列,然后是行。如果在列变化中,f.i.,恰好已经有另一个控件,这个另一个控件被推到一边,即使它的单元格不是移动控件的目标单元格的最终位置。

    Begin/EndUpdate 将不起作用,控制集合从不检查更新计数。你能做的是使用受保护的黑客来访问控制项的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”之前测试目标单元格是否为空,具体取决于您期望的正确控制移动。

    【讨论】:

    • THackControlItem 如果单元格为空,则可以正常工作。我一直在将对齐设置为 alClient 的单元格中使用 TButtons,因此如果单元格不为空,我不会将按钮放在网格面板单元格上,而是实际上放在另一个按钮的顶部。谢谢
    【解决方案2】:

    我使用一种完全不同的方式来完成这项工作...创建一个完整的单元只是为了向ExtCtrls.TControlCollection 添加一个方法而不接触单元ExtCtrls(第一次破解)并使这种方法使用InternalSetLocation(第二次破解)。我还在这篇文章中解释了这两种技巧。

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

    我是这样一步一步做的:

    1. 我将我为此类工作制作的此类单元包含到项目中(添加文件)
    2. 我添加到我的 TForm 界面使用部分这样的单元(或我需要它的地方)
    3. 我使用我的方法AddControlAtCell 而不是ExtCtrls.TControlCollection.AddControl

    这是我为此类工作创建的单元,将其保存为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.
    

    我希望 cmets 足够清楚,请阅读它们以了解我为什么以及如何这样做。

    然后,当我需要在指定单元格的网格面板中添加控件时,我会执行下一个简单的调用:

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

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

    // 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
    

    请注意,我使用了这两个 Hack:

    1. type THackControlItem 让我访问方法InternalSetLocation
    2. type TGridPanel=class(ExtCtrls.TGridPanel) 让我添加一个方法到 ExtCtrls.TGridPanel 甚至不用触摸(也不需要 ExtCtrls 的来源)

    重要提示:还请注意,我提到它需要将单元添加到您要使用方法AddControlAtCell 的每个表单的界面的使用中;那是针对普通人的,高级人员也可以创建另一个单元,等等...“概念”是在声明您要使用它的 GridPanel 之前使用该单元...例如:如果 GridPanel 是在设计时放在表单上......它必须继续实现这种表单单元的使用。

    希望这对其他人有所帮助。

    【讨论】:

    • 这真的很有帮助,非常感谢。我只需要在设置父级之前设置 de AddControlAtCell 值,因为它会创建控件然后销毁它,因此单元格 [0,0] 永远不会有控件。真的不知道这是否只是发生在我身上,但将其留在这里以供将来参考。示例:Boton := TButton.Create(GridPanel1); GridPanel1.AddControlAtCell(Boton,x,y); Boton.Visible := False; Boton.Parent := GridPanel1;
    【解决方案3】:

    以下解决方案无需任何黑客攻击即可工作。

    我的代码在 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;
                    }
               }
            }
         }
    }
    

    【讨论】:

      猜你喜欢
      • 2011-07-18
      • 1970-01-01
      • 2013-05-25
      • 2012-09-29
      • 1970-01-01
      • 2012-08-25
      • 1970-01-01
      • 1970-01-01
      • 2019-10-17
      相关资源
      最近更新 更多