首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# GDI+ 橡皮筋繪圖技巧:滑鼠拖曳即時預覽與重繪機制

     編輯:關於C語言

橡皮筋繪圖

「橡皮筋」是指滑鼠拖曳時即時顯示繪圖預覽,滑鼠釋放後確定圖形。核心挑戰:即時預覽不留痕跡 + 確定後永久保留 + 視窗重繪正確還原。

核心架構

  • PictureBox 承載繪圖面,Dock = Fill 佔滿視窗
  • ArrayList (或 List<Shape>) 儲存已確定圖形
  • Paint 事件統一處理永久圖形重繪
  • MouseDown/Move/Up 處理互動邏輯

完整實作程式碼


using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;

public class RubberBandForm : Form
{
    private PictureBox canvas;
    private List shapes = new List();
    private Point startPt = Point.Empty, endPt = Point.Empty;
    private bool isDrawing = false;
    
    public RubberBandForm()
    {
        canvas = new PictureBox
        {
            Dock = DockStyle.Fill,
            BackColor = Color.White
        };
        canvas.Paint += Canvas_Paint;
        canvas.MouseDown += Canvas_MouseDown;
        canvas.MouseMove += Canvas_MouseMove;
        canvas.MouseUp += Canvas_MouseUp;
        Controls.Add(canvas);
        
        Text = "橡皮筋繪圖示範";
        ClientSize = new Size(600, 400);
    }
    
    // 形狀資料結構
    private class EllipseShape
    {
        public Rectangle Bounds { get; set; }
        public Pen Pen { get; set; } = Pens.Black;
    }
    
    // 重繪:只畫已確定的形狀
    private void Canvas_Paint(object sender, PaintEventArgs e)
    {
        foreach (var shape in shapes)
            e.Graphics.DrawEllipse(shape.Pen, shape.Bounds);
    }
    
    // 滑鼠按下:記錄起點
    private void Canvas_MouseDown(object sender, MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Left)
        {
            isDrawing = true;
            startPt = e.Location;
            endPt = Point.Empty;
        }
    }
    
    // 滑鼠移動:橡皮筋效果
    private void Canvas_MouseMove(object sender, MouseEventArgs e)
    {
        if (!isDrawing) return;
        
        endPt = e.Location;
        
        // 關鍵技巧:使用 ControlPaint.DrawReversibleFrame
        // XOR 模式繪製,再次呼叫即可抹除,不需重繪整個畫布
        using (Graphics g = canvas.CreateGraphics())
        {
            // 抹除上一次預覽 (若存在)
            if (endPt != Point.Empty)
            {
                Rectangle prevRect = GetRect(startPt, endPt);
                ControlPaint.DrawReversibleFrame(
                    canvas.RectangleToScreen(prevRect), Color.Black, FrameStyle.Dashed);
            }
            
            // 繪製新預覽
            Rectangle currRect = GetRect(startPt, e.Location);
            ControlPaint.DrawReversibleFrame(
                canvas.RectangleToScreen(currRect), Color.Black, FrameStyle.Dashed);
        }
    }
    
    // 滑鼠釋放:確定圖形
    private void Canvas_MouseUp(object sender, MouseEventArgs e)
    {
        if (!isDrawing) return;
        isDrawing = false;
        
        // 抹除最後一次預覽
        if (endPt != Point.Empty)
        {
            Rectangle prevRect = GetRect(startPt, endPt);
            ControlPaint.DrawReversibleFrame(
                canvas.RectangleToScreen(prevRect), Color.Black, FrameStyle.Dashed);
        }
        
        // 建立永久形狀
        Rectangle finalRect = GetRect(startPt, e.Location);
        if (finalRect.Width > 0 && finalRect.Height > 0)
        {
            shapes.Add(new EllipseShape { Bounds = finalRect });
            canvas.Invalidate();  // 觸發 Paint 事件重繪
        }
        
        startPt = Point.Empty;
        endPt = Point.Empty;
    }
    
    // 輔助:由兩點計算矩形 (處理負寬高)
    private Rectangle GetRect(Point p1, Point p2)
    {
        return new Rectangle(
            Math.Min(p1.X, p2.X), Math.Min(p1.Y, p2.Y),
            Math.Abs(p1.X - p2.X), Math.Abs(p1.Y - p2.Y));
    }
    
    [STAThread]
    static void Main() => Application.Run(new RubberBandForm());
}

關鍵技術點解析

1. ControlPaint.DrawReversibleFrame — 橡皮筋核心

  • XOR 模式直接在螢幕座標繪製,不屬於特定控制項
  • 同樣參數呼叫兩次 = 繪製後抹除,無殘留
  • 需轉換為螢幕座標:control.RectangleToScreen(rect)
  • FrameStyle.Dashed 虛線框提示「預覽狀態」

2. Paint 事件分離永久繪製

  • 永久圖形只在 Paint 事件中透過 e.Graphics 繪製
  • Invalidate() 觸發重繪,而非直接呼叫 CreateGraphics()
  • 視窗最小化/遮蔽/恢復時自動正確重繪

3. 雙緩衝防止閃爍


// 在建構函式或 Load 事件啟用
canvas.SetStyle(ControlStyles.AllPaintingInWmPaint | 
                ControlStyles.UserPaint | 
                ControlStyles.DoubleBuffer, true);

擴充:支援多種圖形


abstract class Shape
{
    public abstract void Draw(Graphics g);
    public abstract bool Contains(Point pt);  // 點選測試
}

class RectangleShape : Shape
{
    public Rectangle Bounds { get; set; }
    public Pen Pen { get; set; }
    public override void Draw(Graphics g) => g.DrawRectangle(Pen, Bounds);
    public override bool Contains(Point pt) => Bounds.Contains(pt);
}

class LineShape : Shape
{
    public Point Start { get; set; }
    public Point End { get; set; }
    public Pen Pen { get; set; }
    public override void Draw(Graphics g) => g.DrawLine(Pen, Start, End);
    public override bool Contains(Point pt) => /* 線段距離檢測 */ false;
}

常見錯誤與修正

錯誤做法問題正確做法
MouseMove 中 CreateGraphics() 直接畫視窗重繪時消失、殘留痕跡用 DrawReversibleFrame 或雙緩衝繪製至位圖
不儲存形狀資料,只畫在畫布上無法重繪、無法編輯、無法儲存資料模型 (List<Shape>) 與視圖 (Paint) 分離
用實線畫預覽難以區分預覽與確定圖形虛線框、不同顏色、半透明筆刷

本章小結

  • 橡皮筋繪圖核心:DrawReversibleFrame XOR 模式預覽 + Paint 事件永久繪製
  • 資料模型 (形狀清單) 與視圖分離,確保重繪正確性
  • 雙緩衝 (DoubleBuffer) 消除閃爍
  • 擴充性:抽象 Shape 基類支援多圖形、編輯、點選、序列化
  • 現代替代:WPF InkCanvas、Adorner 層提供更豐富的繪圖基礎設施
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved