記錄滑鼠按下時相對視窗左上角的偏移量,滑鼠移動時將視窗位置設為「螢幕滑鼠位置 - 偏移量」。
public partial class BorderlessForm : Form
{
private Point _mouseOffset;
public BorderlessForm()
{
InitializeComponent();
this.FormBorderStyle = FormBorderStyle.None; // 移除標題列與邊框
this.MouseDown += BorderlessForm_MouseDown;
this.MouseMove += BorderlessForm_MouseMove;
}
private void BorderlessForm_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
// 記錄滑鼠在視窗客戶區內的相對座標
_mouseOffset = new Point(-e.X, -e.Y);
}
}
private void BorderlessForm_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
// 取得螢幕絕對座標並套用偏移
Point screenPos = Control.MousePosition;
screenPos.Offset(_mouseOffset.X, _mouseOffset.Y);
this.Location = screenPos;
}
}
}
public class DraggableBorderlessForm : Form
{
private Point _mouseOffset;
private bool _isDragging;
private readonly Rectangle _dragRegion; // 可拖曳區域(如標題列高度 30px)
public DraggableBorderlessForm() : this(new Rectangle(0, 0, int.MaxValue, 30)) { }
public DraggableBorderlessForm(Rectangle dragRegion)
{
InitializeComponent();
this.FormBorderStyle = FormBorderStyle.None;
_dragRegion = dragRegion;
this.MouseDown += OnMouseDown;
this.MouseMove += OnMouseMove;
this.MouseUp += OnMouseUp;
}
private void OnMouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left && _dragRegion.Contains(e.Location))
{
_mouseOffset = new Point(-e.X, -e.Y);
_isDragging = true;
this.Capture = true; // 捕獲滑鼠,即使移出客戶區仍收到事件
}
}
private void OnMouseMove(object sender, MouseEventArgs e)
{
if (_isDragging)
{
Point screenPos = Control.MousePosition;
screenPos.Offset(_mouseOffset.X, _mouseOffset.Y);
// 螢幕邊界限制(可選)
Rectangle workingArea = Screen.FromPoint(screenPos).WorkingArea;
screenPos.X = Math.Clamp(screenPos.X, workingArea.Left, workingArea.Right - this.Width);
screenPos.Y = Math.Clamp(screenPos.Y, workingArea.Top, workingArea.Bottom - this.Height);
this.Location = screenPos;
}
}
private void OnMouseUp(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
_isDragging = false;
this.Capture = false;
}
}
}
| 細節 | 原始碼做法 | 進階做法 |
|---|---|---|
| 偏移量計算 | new Point(-e.X, -e.Y) | 同樣,但建議改用 Point.Subtract(e.Location, new Size(this.Location)) 更直觀 |
| 螢幕座標取得 | Control.MousePosition | 同,或 Cursor.Position |
| 滑鼠捕獲 | 無 | this.Capture = true/false 確保拖曳不中斷 |
| 拖曳區域限制 | 全客戶區可拖曳 | 矩形區域 _dragRegion.Contains(e.Location) 模擬標題列 |
| 螢幕邊界限制 | 無 | Screen.WorkingArea + Math.Clamp |
| 高 DPI 縮放 | 可能位置偏移 | 啟用 Per-Monitor DPI Awareness,或使用邏輯像素一致 |
this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer, true);。Screen.WorkingArea。Screen.FromPoint(screenPos) 動態取得目標螢幕工作區,自動適配不同解析度/DPI。Control.Capture = true 統一由表單處理。WindowChrome 類別可自訂標題列高度、按鈕、拖曳區域,保留原生視窗管理(Aero Snap、最小化動畫)。AppWindow + AppWindowTitleBar 完全客製化標題列,支援 Mica/Acrylic 材質。無標題列視窗拖曳核心只有 10 行程式碼,但邊界限制、滑鼠捕獲、高 DPI、子控制項事件轉發才是決定體驗好壞的關鍵。建議封裝為可重用基底類別 DraggableBorderlessForm,專案統一繼承,一次解決、處處受益。