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来定位。这里以绘制表格来举例。为了更直观的看到以鼠标为中心缩放的效果,表格中专门绘制了一个十字中心。
转载请注明出处。