简单的平移 PictureBox






4.75/5 (7投票s)
一个基本的示例,展示如何构建一个具有简单平移和缩放功能的 PictureBox 控件。
引言
很多时候,我在论坛上看到有人发帖求助,希望给 .NET 的 PictureBox
控件添加平移功能。在反编译了框架中的类后,我意识到它是一个相当简单的控件,最好的做法是继承 Control
并从头开始编写一个。在回复了用户关于这个主题的几十个帖子后,我决定写一个**非常**简单的示例,展示如何实现它,而不涉及任何复杂的内容,以便在基本理解的基础上进行扩展。请不要纠结于这篇文章*缺少*了哪些重要功能,因为这篇文章并不是在炫耀一个功能齐全的控件;它的唯一目的是帮助理解使用 GDI+ 平移图像的概念。
背景
.NET Framework 的 PictureBox
控件使用对 GDI+ 的 DrawImage
方法的简单调用。本文建议使用相同的方法,但使用多态性,接受一个代表图像的一部分的矩形而不是整个图像来绘制。移动这个矩形会使图像的该部分被绘制到目标矩形,从而提供简单的平移效果。
工作原理
我认为不涵盖每一行代码很重要,因为其中大部分都非常基础,但我会尝试概述构成该概念的一些重要部分,并澄清一些我似乎总是在论坛上回答的基本问题。
继承自 Control
编写自定义控件的第一步是决定从哪里开始。如果框架中有一个控件具有您想要的功能,而您只想在此基础上添加一些东西,那么继承该控件可能是一个好主意。但在许多情况下,您想做一些新的事情,或者自己来实现。在这种情况下,您将需要继承 Windows.Forms.Control
基类。
class DDPanBox : System.Windows.Forms.Control
{
public DDPanBox()
{
...
}
...
}
绘制图像
假设创建一个变量来保存图像足够基础,无需在此涵盖,那么绘制图像的想法很简单。只需在 Paint
事件中使用 Control
的 Graphics
对象调用 DrawImage
() 方法,并将要绘制的图像、要绘制的图像以及需要绘制的图像区域传递进去。
protected override void OnPaint(PaintEventArgs e)
{
if (_Image != null)
{
e.Graphics.InterpolationMode =
System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
e.Graphics.SmoothingMode =
System.Drawing.Drawing2D.SmoothingMode.HighQuality;
e.Graphics.DrawImage(_Image, ClientRectangle,
DrawRect, GraphicsUnit.Pixel);
if(zoom)
e.Graphics.DrawString("Zoom 2:1", this.Font,
Brushes.White, new PointF(15F, 15F));
else
e.Graphics.DrawString("Zoom 1:1", this.Font,
Brushes.White, new PointF(15F, 15F));
}
base.OnPaint(e);
}
我在这里设置 Interpolationmode
和 SmoothingMode
只是为了让缩放后的图像看起来更好;这不是必需的,删除这些行也可以提高应用程序的速度。
e.Graphics.DrawImage(_Image, ClientRectangle, DrawRect, GraphicsUnit.Pixel);
这里的 DrawImage
方法接受 _Image
(要绘制的图像)、ClientRectangle
(绘制图像的目标矩形,在本例中是控件的整个表面)、DrawRect
(代表需要绘制的图像部分的矩形),以及 GraphicsUnit.Pixel
(要绘制图像的比例格式)。
移动 DrawRect(源矩形)
这里的基本思想是捕获鼠标按下事件,获取单击位置并将其保存到变量中,然后设置一个标志表示拖动已激活。然后,在 MouseMove
事件中,检查该标志。如果为 true
,则获取当前鼠标位置与保存位置之间的差值,然后将该差值应用于 drawRect
。之后,检查 drawRect
是否超出了图像边界,如果是,则将其移到边缘。
bool dragging = false; //Tells us if our image has been clicked on.
Point start = new Point(); //Keep initial click for accurate panning.
void panBox_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
dragging = true;
//offset new point by original one so
//we know where in the image we are.
start = new Point(e.Location.X + DrawRect.Location.X,
e.Location.Y + DrawRect.Location.Y);
...
}
else if (e.Button == MouseButtons.Right)
{
...
}
}
void panBox_MouseMove(object sender, MouseEventArgs e)
{
if (dragging)
{
DrawRect.Location = new Point(start.X - e.Location.X,
start.Y - e.Location.Y);
if (DrawRect.Location.X < 0 -Padding.Left)
DrawRect.Location = new Point(0 - Padding.Left,
DrawRect.Location.Y);
if (DrawRect.Location.Y < 0 - Padding.Top)
DrawRect.Location = new Point(DrawRect.Location.X, 0 - Padding.Top);
if (DrawRect.Location.X > _Image.Width - DrawRect.Width + Padding.Right)
DrawRect.Location = new Point(_Image.Width - DrawRect.Width +
Padding.Right, DrawRect.Location.Y);
if (DrawRect.Location.Y > _Image.Height - DrawRect.Height +
Padding.Bottom)
DrawRect.Location = new Point(DrawRect.Location.X,
_Image.Height - DrawRect.Height +
Padding.Bottom);
this.Refresh();
}
}
void panBox_MouseUp(object sender, MouseEventArgs e)
{
dragging = false;
...
}
保持界面美观!
当窗体大小调整时,您会发现控件的显示效果并不好。因此,我们需要处理调整大小事件,并确保 drawRect
不会脱离图像。
void panBox_Resize(object sender, EventArgs e)
{
if (_Image != null)
{
if (zoom)
{
DrawRect = new Rectangle(DrawRect.Location.X,
DrawRect.Location.Y, ClientRectangle.Width / 2,
ClientRectangle.Height / 2);
}
else
DrawRect = new Rectangle(DrawRect.Location.X,
DrawRect.Location.Y, ClientRectangle.Width, ClientRectangle.Height);
if (DrawRect.Location.X < 0 - Padding.Left)
DrawRect.Location = new Point(0 - Padding.Left, DrawRect.Location.Y);
if (DrawRect.Location.Y < 0 - Padding.Top)
DrawRect.Location = new Point(DrawRect.Location.X, 0 - Padding.Top);
if (DrawRect.Location.X > _Image.Width - DrawRect.Width + Padding.Right)
DrawRect.Location = new Point(_Image.Width - DrawRect.Width +
Padding.Right, DrawRect.Location.Y);
if (DrawRect.Location.Y > _Image.Height - DrawRect.Height + Padding.Bottom)
DrawRect.Location = new Point(DrawRect.Location.X,
_Image.Height - DrawRect.Height + Padding.Bottom);
this.Refresh();
}
}
这里有一点很重要。如果图像小于客户端矩形,图像将右下对齐。这是因为图像是从完整的源图像矩形绘制的,但绘制到更大的客户端矩形。在我看来,这里最好的解决方案是居中图像。但这超出了本文的范围。源代码并未反映这一点,但一个简单的解决方案可以将图像保留在左上角(如通常预期的那样),只需在调整大小事件处理程序中的 this.Refresh()
行上方添加以下代码块。
if (_Image.Width < ClientRectangle.Width)
{
DrawRect.Location = new Point(0, DrawRect.Location.Y);
}
if (_Image.Height < ClientRectangle.Height)
{
DrawRect.Location = new Point(DrawRect.Location.X, 0);
}
虽然,这可能会在您构建自定义解决方案时带来自己的问题。这就是为什么它没有包含在源代码中。我认为这里的行为应该取决于应用程序和开发者的偏好,因此留给您自行决定。
使用代码
源代码提供了一个简单易用的即插即用控件,可以与 Visual Studio 一起使用,或者像其他任何控件一样实例化。
我不认为这是一个完整的控件,我分享它也不是作为一个完整的控件。它能工作,能满足目的,并且无需修改即可使用。但它仅旨在作为实现更全面、更健壮解决方案的垫脚石。我确实希望它能帮助那些在平移 PictureBox 的基本概念上遇到困难的人。
关注点
有一点很重要,在鼠标按下事件中,当您捕获鼠标位置以在图像上平移源矩形时,您必须将鼠标位置的偏移量设置为当前源矩形。这可以确保当图像的原点与控件的客户端矩形的原点不相同时,图像在平移时不会出现混乱的跳动。代码的摘录如下,这个技巧在注释后的 `start` 变量的设置中得到了说明。
void panBox_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
dragging = true;
//offset new point by original one so
//we know where in the image we are.
start = new Point(e.Location.X + DrawRect.Location.X,
e.Location.Y + DrawRect.Location.Y);
Cursor = Cursors.SizeAll; //just for looks.
}
...
}
历史
- 1.0:首次发布。