The accepted answer is a cool trick, but it doesn't always work if the Form is covered by a Fill-docked child control like a Panel (or derivates) for example, because this control will eat all most Windows messages.
Here is a simple approach that works also in this case: derive the control in question (use this class instead of the standard one) an handle mouse messages like this:
private class MyTableLayoutPanel : Panel // or TableLayoutPanel, etc.
{
private Point _mouseDown;
private Point _formLocation;
private bool _capture;
// NOTE: we cannot use the WM_NCHITTEST / HTCAPTION trick because the table is in control, not the owning form...
protected override void OnMouseDown(MouseEventArgs e)
{
_capture = true;
_mouseDown = e.Location;
_formLocation = ((Form)TopLevelControl).Location;
}
protected override void OnMouseUp(MouseEventArgs e)
{
_capture = false;
}
protected override void OnMouseMove(MouseEventArgs e)
{
if (_capture)
{
int dx = e.Location.X - _mouseDown.X;
int dy = e.Location.Y - _mouseDown.Y;
Point newLocation = new Point(_formLocation.X + dx, _formLocation.Y + dy);
((Form)TopLevelControl).Location = newLocation;
_formLocation = newLocation;
}
}
}