C#中使用Graphics可以很方便的繪圖,在繪完圖后,往往需要對圖進行縮放和移動。縮放時,將鼠標當前的位置作為縮放的中心來縮放,看效果圖
中心縮放的核心在於計算圖形新的原點,請看代碼
public partial class Form1 : Form { #region 內部變量 private Graphics _g = null; private Image _imageCache = null; /// <summary> /// 單元格的寬(100%) /// </summary> private int _cellWidth_px = 100; /// <summary> /// 單元格的高(100%) /// </summary> private int _cellHeight_px = 100; private float _zoomOld = 1.0f; private float _zoom = 1.0f; private float _zoomMin = 0.1f; private float _zoomMax = 1000f; /// <summary> /// 表格的左上角 /// </summary> private PointF _gridLeftTop = new PointF(200, 200); private bool _leftButtonPress = false; private PointF _mousePosition = new PointF(0, 0); #endregion public Form1() { InitializeComponent(); //設置Paint參數以便能更好的控制Paint. SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer, true); this.MouseWheel += Form1_MouseWheel; ; } private void Form1_MouseWheel(object sender, MouseEventArgs e) { var delta = e.Delta; if (Math.Abs(delta) < 10) { return; } var mousePosition = new PointF(); mousePosition.X = e.X; mousePosition.Y = e.Y; _zoomOld = _zoom; if (delta < 0) { _zoom -= FetchStep(delta); } else if (delta > 0) { _zoom += FetchStep(delta); } if (_zoom < _zoomMin) { _zoom = _zoomMin; } else if (_zoom > _zoomMax) { _zoom = _zoomMax; } var zoomNew = _zoom; var zoomOld = _zoomOld; var deltaZoomNewToOld = zoomNew / zoomOld; //計算零點 //任意比例下的鼠標點與零點的距離deltaPO1都等於鼠標點與零點在(0,0)且比例為1時的距離deltaPO乘以縮放比例zoom, //於是有deltaPO1=deltaPO*zoom, //記在第1種縮放比例zoom1下有deltaPO1=deltaPO*zoom1, //記在第2種縮放比例zoom2下有deltaPO2=deltaPO*zoom2, //將上面兩式相比則有deltaPO2/deltaPO1=zoom2/zoom1,令deltaZoomNewToOld=zoom2/zoom1則有deltaPO2/deltaPO1=deltaZoomNewToOld //又鼠標點與零點的距離deltaPO=P(x,y)-O(x,y),代入得 //O.x2=P.x-(P.x-O.x1)*deltaZoomNewToOld; //O.y2=P.y-(P.y-O.y1)*deltaZoomNewToOld; var zero = _gridLeftTop; zero.X = mousePosition.X - (mousePosition.X - zero.X) * deltaZoomNewToOld; zero.Y = mousePosition.Y - (mousePosition.Y - zero.Y) * deltaZoomNewToOld; _gridLeftTop = zero; this.Refresh(); } #region FetchStep /// <summary> /// 獲取縮放的步進 /// </summary> /// <returns></returns> private float FetchStep(float delta) { if (_zoom == 1) { return delta > 0 ? 1 : 0.05f; } else { return _zoom >= 1 ? 1 : 0.05f; } } #endregion private void Form1_Paint(object sender, PaintEventArgs e) { if (_imageCache == null) { _imageCache = new Bitmap(this.Width, this.Height); } if (_g == null) { _g = Graphics.FromImage(_imageCache); } _g.Clear(this.BackColor); DrawGrid(_g); e.Graphics.DrawImage(_imageCache, new Point(0, 0)); } #region DrawGrid /// <summary> /// 繪制表格 /// </summary> /// <param name="g"></param> private void DrawGrid(Graphics g) { float cellWidth = _zoom * _cellWidth_px; float cellHeight = _zoom * _cellHeight_px; //單元格的寬和高最小為1像素 cellWidth = cellWidth < 1 ? 1 : cellWidth; cellHeight = cellHeight < 1 ? 1 : cellHeight; int rowCount = 3; int columnCount = 3; var gridHeight = rowCount * cellHeight; var gridWidth = columnCount * cellWidth; Pen pen = Pens.White; var p1 = new PointF(); var p2 = new PointF(); //繪制橫線 for (int r = 0; r <= rowCount; r++) { p1.X = _gridLeftTop.X; p1.Y = _gridLeftTop.Y + r * cellHeight; p2.X = p1.X + gridWidth; p2.Y = p1.Y; g.DrawLine(pen, p1, p2); } //繪制豎線 for (int c = 0; c <= columnCount; c++) { p1.X = _gridLeftTop.X + c * cellWidth; p1.Y = _gridLeftTop.Y; p2.X = p1.X; p2.Y = p1.Y + gridHeight; g.DrawLine(pen, p1, p2); } //繪制表格的十字中心 pen = Pens.Red; //十字橫線 p1.X = _gridLeftTop.X + gridWidth / 2 - cellWidth / 2; p1.Y = _gridLeftTop.Y + gridHeight / 2; p2.X = p1.X + cellWidth; p2.Y = p1.Y; g.DrawLine(pen, p1, p2); //十字豎線 p1.X = _gridLeftTop.X + gridWidth / 2; p1.Y = _gridLeftTop.Y + gridHeight / 2 - cellHeight / 2; p2.X = p1.X; p2.Y = p1.Y + cellHeight; g.DrawLine(pen, p1, p2); //繪制比例 p1.X = _gridLeftTop.X; p1.Y = _gridLeftTop.Y + gridHeight; g.DrawString($"{_zoom * 100}%", SystemFonts.DefaultFont, Brushes.White, p1); } #endregion private void Form1_MouseMove(object sender, MouseEventArgs e) { var offsetX = e.X - _mousePosition.X; var offsetY = e.Y - _mousePosition.Y; if (_leftButtonPress) { _gridLeftTop.X += offsetX; _gridLeftTop.Y += offsetY; _mousePosition.X = e.X; _mousePosition.Y = e.Y; this.Refresh(); } } private void Form1_MouseDown(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Left) { _mousePosition.X = e.X; _mousePosition.Y = e.Y; _leftButtonPress = true; this.Cursor = Cursors.Hand; } } private void Form1_MouseUp(object sender, MouseEventArgs e) { _leftButtonPress = false; this.Cursor = Cursors.Default; } }注:
1.在鼠標滾動的時候進行縮放,這時重新計算原點,關鍵在於計算縮放前后的比例變化。
2.鼠標左鍵按下並移動鼠標時開始移動圖形,移動圖形只要改變圖形的原點即可。
3.繪制圖形時,圖形內部的所有點都以圖形的原點_gridLeftTop來定位。這里以繪制表格來舉例。為了更直觀的看到以鼠標為中心縮放的效果,表格中專門繪制了一個十字中心。
轉載請注明出處。