65.9K
CodeProject 正在变化。 阅读更多。
Home

简单的平移 PictureBox

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.75/5 (7投票s)

2010年2月19日

CPOL

5分钟阅读

viewsIcon

72155

downloadIcon

3854

一个基本的示例,展示如何构建一个具有简单平移和缩放功能的 PictureBox 控件。

引言

很多时候,我在论坛上看到有人发帖求助,希望给 .NET 的 PictureBox 控件添加平移功能。在反编译了框架中的类后,我意识到它是一个相当简单的控件,最好的做法是继承 Control 并从头开始编写一个。在回复了用户关于这个主题的几十个帖子后,我决定写一个**非常**简单的示例,展示如何实现它,而不涉及任何复杂的内容,以便在基本理解的基础上进行扩展。请不要纠结于这篇文章*缺少*了哪些重要功能,因为这篇文章并不是在炫耀一个功能齐全的控件;它的唯一目的是帮助理解使用 GDI+ 平移图像的概念。

背景

.NET Framework 的 PictureBox 控件使用对 GDI+ 的 DrawImage 方法的简单调用。本文建议使用相同的方法,但使用多态性,接受一个代表图像的一部分的矩形而不是整个图像来绘制。移动这个矩形会使图像的该部分被绘制到目标矩形,从而提供简单的平移效果。

工作原理

我认为不涵盖每一行代码很重要,因为其中大部分都非常基础,但我会尝试概述构成该概念的一些重要部分,并澄清一些我似乎总是在论坛上回答的基本问题。

继承自 Control

编写自定义控件的第一步是决定从哪里开始。如果框架中有一个控件具有您想要的功能,而您只想在此基础上添加一些东西,那么继承该控件可能是一个好主意。但在许多情况下,您想做一些新的事情,或者自己来实现。在这种情况下,您将需要继承 Windows.Forms.Control 基类。

class DDPanBox : System.Windows.Forms.Control
{
    public DDPanBox()
    {
      ...
    }
    ...
}
绘制图像

假设创建一个变量来保存图像足够基础,无需在此涵盖,那么绘制图像的想法很简单。只需在 Paint 事件中使用 ControlGraphics 对象调用 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);
}

我在这里设置 InterpolationmodeSmoothingMode 只是为了让缩放后的图像看起来更好;这不是必需的,删除这些行也可以提高应用程序的速度。

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:首次发布。
© . All rights reserved.