Windows 採用**保留模式**繪圖:視窗被遮擋、最小化、恢復、調整大小時,系統會發送 WM_PAINT 訊息要求重繪。若僅在 Click 事件中呼叫 CreateGraphics().DrawXxx(),繪製內容僅存在於螢幕快取,下一次 WM_PAINT 到來時會被清除(重繪背景),導致圖形消失。
正確做法:在 PictureBox.Paint 或 Form.Paint 事件中,根據資料模型(List<Shape>)繪製所有圖形。使用者操作只修改資料模型,再呼叫 Invalidate() 觸發重繪。
public abstract class Shape {
public Rectangle Bounds { get; set; }
public Color Color { get; set; } = Color.Black;
public abstract void Draw(Graphics g);
public abstract bool Contains(Point p); // 用於點選/拖曳判斷
}
public class RectShape : Shape {
public override void Draw(Graphics g) => g.DrawRectangle(new Pen(Color), Bounds);
public override bool Contains(Point p) => Bounds.Contains(p);
}
public class EllipseShape : Shape {
public override void Draw(Graphics g) => g.DrawEllipse(new Pen(Color), Bounds);
public override bool Contains(Point p) => Bounds.Contains(p); // 簡易矩形碰撞
}
public class LineShape : Shape {
public Point EndPoint { get; set; }
public override void Draw(Graphics g) => g.DrawLine(new Pen(Color), Bounds.Location, EndPoint);
public override bool Contains(Point p) => /* 線段距離判斷 */ false;
}
public partial class DrawForm : Form {
private List<Shape> shapes = new List<Shape>();
private Shape currentShape;
private Point startPoint;
private Shape selectedShape;
private Point dragOffset;
public DrawForm() {
InitializeComponent();
pictureBox1.Paint += PictureBox1_Paint;
pictureBox1.MouseDown += PictureBox1_MouseDown;
pictureBox1.MouseMove += PictureBox1_MouseMove;
pictureBox1.MouseUp += PictureBox1_MouseUp;
// 雙緩衝
pictureBox1.GetType().GetProperty("DoubleBuffered",
System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)
?.SetValue(pictureBox1, true);
}
private void PictureBox1_Paint(object sender, PaintEventArgs e) {
foreach (var s in shapes) s.Draw(e.Graphics);
// 繪製正在拖曳建立中的形狀
if (currentShape != null) currentShape.Draw(e.Graphics);
}
private void PictureBox1_MouseDown(object sender, MouseEventArgs e) {
startPoint = e.Location;
// 檢查是否點選現有形狀(從最上層往下找)
for (int i = shapes.Count - 1; i >= 0; i--) {
if (shapes[i].Contains(e.Location)) {
selectedShape = shapes[i];
dragOffset = new Point(e.X - selectedShape.Bounds.X, e.Y - selectedShape.Bounds.Y);
return;
}
}
// 未點中 → 建立新形狀(依工具列選擇)
currentShape = CreateShapeByTool(); // 回傳 RectShape/EllipseShape...
currentShape.Bounds = new Rectangle(startPoint, Size.Empty);
}
private void PictureBox1_MouseMove(object sender, MouseEventArgs e) {
if (selectedShape != null) {
// 拖曳現有形狀
selectedShape.Bounds = new Rectangle(e.X - dragOffset.X, e.Y - dragOffset.Y,
selectedShape.Bounds.Width, selectedShape.Bounds.Height);
pictureBox1.Invalidate(); // 觸發 Paint 重繪
} else if (currentShape != null) {
// 調整新形狀大小
int w = Math.Abs(e.X - startPoint.X);
int h = Math.Abs(e.Y - startPoint.Y);
currentShape.Bounds = new Rectangle(
Math.Min(startPoint.X, e.X), Math.Min(startPoint.Y, e.Y), w, h);
pictureBox1.Invalidate();
}
}
private void PictureBox1_MouseUp(object sender, MouseEventArgs e) {
if (currentShape != null) {
if (currentShape.Bounds.Width > 5 && currentShape.Bounds.Height > 5)
shapes.Add(currentShape);
currentShape = null;
}
selectedShape = null;
pictureBox1.Invalidate();
}
// 存檔為圖片
private void btnSave_Click(object sender, EventArgs e) {
using (var bmp = new Bitmap(pictureBox1.Width, pictureBox1.Height)) {
pictureBox1.DrawToBitmap(bmp, pictureBox1.ClientRectangle);
bmp.Save("drawing.png", System.Drawing.Imaging.ImageFormat.Png);
}
}
}
| 問題 | 解決方案 |
|---|---|
| 圖形被遮擋後消失 | 所有繪圖邏輯集中在 Paint 事件,靠 Invalidate() 觸發重繪 |
| DrawToBitmap 存出白圖 | 啟用 DoubleBuffered = true,並確保在 Paint 中繪製,而非 CreateGraphics() |
| 拖曳殘影/閃爍 | 雙緩衝 + Invalidate() 整個區域,或計算舊/新矩形聯集只重繪變更區 |
| 刪除形狀 | 從 shapes 移除項目 → Invalidate() |
GraphicsPath 實現精確點選(含旋轉、彎曲形狀)。WinForms 繪圖核心在於**「資料驅動視圖」**:維護形狀集合,Paint 事件遍歷繪製,互動只修改集合並 Invalidate()。配合雙緩衝即可獲得流暢、無殘影、可存檔的繪圖體驗。