在 WinForms 應用中,使用 PictureBox 控件繪製圖形(如點、線)時,直接調用 CreateGraphics() 繪製的內容僅存在於屏幕緩衝區,無法隨 Image.Save() 一起保存到文件。本文提供一種通用的「繪製記錄+重繪」模式來解決此問題。
關鍵點: 所有繪製操作必須作用於 Bitmap 對象(即 PictureBox.Image),而不是控件的顯示表面。做法是維護一個「繪製指令集合」(如點座標列表),在需要顯示或保存時,統一在 Bitmap 上重繪所有指令。
public partial class Form1 : Form
{
private List<Point> drawPoints = new List<Point>();
private Bitmap canvasBitmap;
public Form1()
{
InitializeComponent();
// 初始化畫布
canvasBitmap = new Bitmap(pictureBox1.Width, pictureBox1.Height);
pictureBox1.Image = canvasBitmap;
}
}
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
drawPoints.Add(e.Location);
RedrawCanvas(); // 重繪整個畫布
}
}
private void RedrawCanvas()
{
using (Graphics g = Graphics.FromImage(canvasBitmap))
{
g.Clear(Color.White); // 清空背景
foreach (var pt in drawPoints)
{
g.FillEllipse(Brushes.Red, pt.X, pt.Y, 4, 4); // 繪製所有點
}
}
pictureBox1.Invalidate(); // 刷新顯示
}
private void btnSave_Click(object sender, EventArgs e)
{
using (SaveFileDialog sfd = new SaveFileDialog())
{
sfd.Filter = "BMP 文件|*.bmp|PNG 文件|*.png|JPEG 文件|*.jpg";
if (sfd.ShowDialog() == DialogResult.OK)
{
canvasBitmap.Save(sfd.FileName); // 直接保存,包含所有繪製內容
}
}
}
若需支持線條、矩形、文字等,可定義統一的繪製指令接口:
public interface IDrawCommand
{
void Execute(Graphics g);
}
public class DrawPoint : IDrawCommand
{
public Point Location { get; set; }
public Color Color { get; set; } = Color.Red;
public int Size { get; set; } = 4;
public void Execute(Graphics g)
{
g.FillEllipse(new SolidBrush(Color), Location.X, Location.Y, Size, Size);
}
}
// 在 RedrawCanvas 中遍歷 List 調用 Execute
| 錯誤做法 | 後果 | 正確做法 |
|---|---|---|
使用 pictureBox.CreateGraphics() 繪製 | 內容不持久,最小化/遮擋後消失,無法保存 | 繪製到 PictureBox.Image (Bitmap) 上 |
| 保存時新建 Bitmap 並縮放 | 畫質下降,座標偏移 | 直接保存原始 canvasBitmap |
| 不清空畫布直接疊加繪製 | 殘留舊痕跡 | 每次重繪前 g.Clear() |
採用「數據驅動繪製」模式:將繪製邏輯與顯示分離,所有圖形信息存儲在集合中,統一在 Bitmap 上渲染。這不僅解決了保存問題,還為實現撤銷/重做、圖層管理、導出矢量格式奠定了基礎。