How to Move and Resize Controls at Run Time (in Delphi Applications)

Man at computer
Hero Images/Getty Images

Here's how to enable dragging and resizing controls (on a Delphi form) with mouse, while the application is running.

Form editor at run-time

Once you place a control (visual component) on the form, you can adjust its position, size, and other design-time properties. 
There are situations, though, when you have to allow a user of your application to reposition form controls and change their size, at run-time.

To enable runtime user movement and resizing of controls on a form with a mouse, three mouse related events need special handling: OnMouseDown, OnMouseMove and OnMouseUp.

In theory...
Let's say you want to enable a user to move (and resize) a button control, with mouse, at run-time. Firstly, you handle the OnMouseDown event to enable the user to "grab" the button. Next, the OnMouseMove event should reposition (move, drag) the button. Finally, OnMouseUp should finish the move operation.

Dragging and resizing form controls ... in practice

Firstly, drop several controls on a form. Have a CheckBox to enable or disable moving and resizing controls at run time.

Next, define three procedures (in the interface section of the form declaration) that will handle mouse events as described above:

  TForm1 = class(TForm)
  procedure ControlMouseDown(Sender: TObject; 
                             Button: TMouseButton; 
                             Shift: TShiftState; 
                             X, Y: Integer);
  procedure ControlMouseMove(Sender: TObject; 
                             Shift: TShiftState; 
                             X, Y: Integer);
  procedure ControlMouseUp(Sender: TObject; 
                           Button: TMouseButton; 
                           Shift: TShiftState; 
                           X, Y: Integer);
  inReposition : boolean;
  oldPos : TPoint;                        

Note: Two form level variables are required to mark if control movement is taking place (inReposition) and to store control old position (oldPos).

In the form's OnLoad event, assign mouse event handling procedures to corresponding events (for those controls you want to be draggable/resizable):

procedure TForm1.FormCreate(Sender: TObject);
  Button1.OnMouseDown := ControlMouseDown;
  Button1.OnMouseMove := ControlMouseMove;
  Button1.OnMouseUp := ControlMouseUp;

  Edit1.OnMouseDown := ControlMouseDown;
  Edit1.OnMouseMove := ControlMouseMove;
  Edit1.OnMouseUp := ControlMouseUp;

  Panel1.OnMouseDown := ControlMouseDown;
  Panel1.OnMouseMove := ControlMouseMove;
  Panel1.OnMouseUp := ControlMouseUp;

  Button2.OnMouseDown := ControlMouseDown;
  Button2.OnMouseMove := ControlMouseMove;
  Button2.OnMouseUp := ControlMouseUp;
end; (*FormCreate*)

Note: the above code enables run-time reposition of Button1, Edit1, Panel1 and Button2.

Finally, here's the magic code:

procedure TForm1.ControlMouseDown(
  Sender: TObject;
  Button: TMouseButton;
  Shift: TShiftState;
  X, Y: Integer);
  if (chkPositionRunTime.Checked) AND 
     (Sender is TWinControl) then
end; (*ControlMouseDown*)

ControlMouseDown in short: once a user presses a mouse button over a control, if run-time reposition is enabled (check box chkPositionRunTime is Checked) and the control that received the mouse down even is derived from TWinControl, mark that control reposition is taking place (inReposition:=True) and make sure all mouse processing is captured for the control - to prevent default "click" events from being processed.

procedure TForm1.ControlMouseMove(
  Sender: TObject;
  Shift: TShiftState;
  X, Y: Integer);
  minWidth = 20;
  minHeight = 20;
  newPos: TPoint;
  frmPoint : TPoint;
  if inReposition then
    with TWinControl(Sender) do

      if ssShift in Shift then
      begin //resize
        Screen.Cursor := crSizeNWSE;
        frmPoint := ScreenToClient(Mouse.CursorPos);
        if frmPoint.X > minWidth then 
          Width := frmPoint.X;
        if frmPoint.Y > minHeight then 
          Height := frmPoint.Y;
      else //move
        Screen.Cursor := crSize;
        Left := Left - oldPos.X + newPos.X;
        Top := Top - oldPos.Y + newPos.Y;
        oldPos := newPos;
end; (*ControlMouseMove*)

ControlMouseMove in short: change the Screen Cursor to reflect the operation: if the Shift key is pressed allow control resizing, or simply move the control to a new position (where the mouse is going). Note: minWidth and minHeight constants provide a sort of size constraint (minimum control width and height).

When the mouse button is released, dragging or resizing is over:

procedure TForm1.ControlMouseUp(
  Sender: TObject;
  Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
  if inReposition then
    Screen.Cursor := crDefault;
    inReposition := False;
end; (*ControlMouseUp*)

ControlMouseUp in short: when a user has finished moving (or resizing the control) release the mouse capture (to enable default click processing) and mark that reposition is finished.

And that does it! Download the sample application and try for yourself.

Note: Another way to move controls at run-time is to use Delphi's drag and drop related properties and methods (DragModeOnDragDropDragOverBeginDrag, etc.). Dragging and dropping can be used to let users drag items from one control - such as a list box or tree view - into another.

How to remember control position and size?

If you allow a user to move and resize form controls, you have to ensure that control placement is somehow saved when the form is closed and that each control's position is restored when the form is created / loaded. Here's how to store the Left, Top, Width and Height properties, for every control on a form, in an INI file.

How about 8 Size Handles?

When you allow a user to move and resize controls on Delphi form, at run-time using the mouse, to fully mimic the design-time environment, you should add eight size handles to the control being resized.