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

旋转图片托盘

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.29/5 (9投票s)

2009年3月31日

CPOL

10分钟阅读

viewsIcon

62598

downloadIcon

1359

允许用户通过从旋转托盘中选择图片来查看图片集。

引言

本文将向您展示如何使用 C# 在旋转托盘上显示图片。有了这段代码,您将能够快速地将一堆图片放到屏幕上,并允许用户旋转托盘来查看所选图片,甚至可以点击图片并在单独的窗体上弹出。想象一个您可以放置在屏幕任何位置、以任何速度旋转或随意调整大小的隐形旋转托盘。然后,想象将图片固定在这个托盘上,当它们移到后面时会缩小,被其他图片遮挡,或者当它们移到前面时会放大并显得更近。无法想象吗?那么,现在您不必再想象了,因为这段代码会为您实现。

我们不会在这里解决任何经济危机或拯救任何小海豹。我们所拥有的只是一个易于使用的 Windows 对象,它将为您的项目增添一抹光彩和活力。

以下是截图...

漂亮吧?

Using the Code

您需要下载并编译 cLibPicTraycLibPicBoxViewer(我在上一篇文章中描述过),然后将它们的 DLL 包含到您自己的项目中。下面列出了如何使用此程序的示例代码。它假定您有一个 C# Windows 窗体,并且已经添加了一个名为“btnAddPicture”的按钮。

namespace formTestCLibPicTray
{
    public partial class formTestCLibPicTray : Form
    {
        /// <summary>
        ///  be sure to include the two DLLs : cLibPicBoxViewer + cLibPicTray
        ///  before running this program.
        /// </summary>

        cLibPicTray.cLibPicTray picTray = new cLibPicTray.cLibPicTray();

        public formTestCLibPicTray()
        {
            InitializeComponent();
            Controls.Add(picTray.picBackground);
            picTray.picBackground.Dock = DockStyle.Fill;

            btnAddPicture.Click += new EventHandler(btnAddPicture_Click);
        }

        void btnAddPicture_Click(object sender, EventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog();
            if (ofd.ShowDialog() == DialogResult.OK)
            {
                PictureBox thisPicBox = new PictureBox();
                thisPicBox.Load(ofd.FileName);
                picTray.addPicBox(ref thisPicBox);
            }
        }
    }
}

如果我们查看按钮“click”事件的事件处理程序,您会注意到我们首先创建了一个名为 ofdOpenFileDialog,然后只有在向用户“显示”此对象后,用户用鼠标按下“确定”按钮(这意味着已选择文件)时,我们才使用结果。接下来,我们创建一个 PictureBox 并加载用户选择的图片,最后,我们通过调用其 .addPicBox() 方法并使用“ref”(通过引用参数)关键字发送我们新创建的 PictureBox 来告诉图片托盘将此图片添加到自身。

就是这样。

如果您想使用 formTestCLibPicTray.zip 文件,您会发现所有代码都打包在这个文件中,您可以下载并直接执行,无需任何准备。但是,如果您想使用 DLL 文件,您需要一步一步地完成。也就是说,

  1. 下载所有 zip 文件。
  2. 打开并编译 PicBoxViewer 为 DLL。
  3. 在您的 C# 集成开发环境中打开 PicTray 解决方案,并将您的 PicBoxViewer 目录中的 DLL 文件(您在上面第 2 步中编译的)包含到此项目中,然后您才能将 PicTray 编译为它自己的 DLL。
  4. 现在您有了这两个 DLL,您可以将整个功能包用到您想要的任何项目中!

PicViewer DLL 可以独立运行,但 PicTray 需要 PicViewer,因此当您运行 PicTray 时,您必须同时包含这两个动态链接库。

有几个选项可供您选择,最简单的选项是:

picTray.bolPopUpClickedPic = true;
picTray.bolMouseMoveOverAutoSelect = true;
picTray.setBackGroundImage(ofd.FileName);

如果您希望当用户点击图片时,该图片的副本出现在单独的弹出窗体中,则将您的 picTraybolPopUpClickedPic 设置为 true。如果您希望用户鼠标悬停的图片被选中而无需点击,则将 bolMouseMoveOverAutoSelect 设置为 true,并且要让图片出现在背景 PictureBox 中的其他图片后面,您可以将图片、文件名或 URL 发送到 setBackGroundImage 方法。

还有其他变量可以玩。您可以在背景上移动托盘,设置图片从前到后(反之亦然)移动时的最大/最小尺寸,以及设置调整托盘半径的选项。如果您想把玩一下,只需写下您的 picTray 对象的名称,让智能感知为您提供选项。然后,如果您想将所有内容重置回标准参数,也有一个函数可以做到。

关注点

您对它的工作原理感兴趣吗?

public struct udtTrayLoc
{
    public double angle;
    public double x;
    public double y;
}

public struct udtPicBoxTrayElement
{
    public PictureBox picBox;
    public udtTrayLoc trayLoc;
    public double dblXOverYRatio;
}

public udtPicBoxTrayElement[] udrPicBoxTrayElements;

用户定义类型 udtPicBoxTrayElement 的数组 udrPicBoxTrayElement 是您的朋友。简而言之:

  1. 此数组中的每个元素都保存了托盘中每张图片的信息、picBox 和它包含的图片。
    • 关于该图片在托盘上位置的信息。
    • 图片的宽高比(宽度除以高度)。
  2. 图片均匀地分布在一个不可见的圆形托盘周围。
  3. 当托盘旋转并重新定位时,所有图片都会从后到前进行排序和放置在屏幕上,并从后面最小的图片到前面最大的图片进行调整大小。

让我们看看添加图片函数。

public void addPicBox(ref PictureBox PicThisBox)
{
    if (udrPicBoxTrayElements == null)
        udrPicBoxTrayElements = new udtPicBoxTrayElement[1];
    else
        Array.Resize<udtpicboxtrayelement>(ref udrPicBoxTrayElements,
                udrPicBoxTrayElements.Length + 1);
            
    udrPicBoxTrayElements[udrPicBoxTrayElements.Length - 1].picBox = PicThisBox;
    picBackground.Controls.Add(
       udrPicBoxTrayElements[udrPicBoxTrayElements.Length - 1].picBox
                             );

    PicThisBox.SizeMode = PictureBoxSizeMode.AutoSize;
    udrPicBoxTrayElements[udrPicBoxTrayElements.Length - 1].dblXOverYRatio
               = (double)udrPicBoxTrayElements[
                                   udrPicBoxTrayElements.Length - 1
                                              ].picBox.Width 
               / (double)udrPicBoxTrayElements[
                                   udrPicBoxTrayElements.Length - 1
                                              ].picBox.Height;

    PicThisBox.SizeMode = PictureBoxSizeMode.StretchImage;
    handle_SizeChange();
    intSelected = udrPicBoxTrayElements.Length - 1;
    repositionTray();
    handle_newSelection();
}

首先,我们通过调整大小(如果为空则初始化)在我们的托盘元素数组中创建一个新条目。然后,我们将函数参数中收到的新添加的图片框存储到此新数组元素的名为“.picBox”的字段中,并使用 Controls.Add 函数将其添加到 picBackground

完成之后,我们需要做一些数学运算,并费力地计算图片的宽高比。为此,我们将 PictureBox 的模式设置为“AutoSize”;这将使图片框自行拉伸到其自然比例。然后,我们强制将图片的宽度和高度值转换为 double 类型,将它们相除以获得它们的比率,并将此值存储在我们数组元素的 dblXOverYRatio 字段中。代码行看起来有点复杂,因为它跨越了几行,但如果你仔细看,它实际上只是相同图片框的宽度除以高度。

一旦我们计算出这个值,我们将 SizeMode 重置为 StretchImage,这样我们现在就可以自己决定它的大小了。请记住,图片现在将拉伸到 PictureBox 的任何大小。这意味着如果您忽略保持图片的宽高比,图片将显得失真。

女士们先生们,独一无二的 repositionTray() 函数!

所以,大部分工作都在这里完成。每当我们添加图片、删除图片、调整背景图片大小或旋转托盘时,repositionTray() 都会发挥作用。

我们有一个类型为“double”的变量,名为“dblCurrentAngle”。这是托盘旋转的角度。还记得我前面提到的每张图片都有一个“在托盘上的位置”吗?好吧,当我们添加/删除图片时,托盘上的所有图片都会让出空间,可以说是“推推搡搡”,并围绕我们圆的 2π 弧度重新定位自己,使它们均匀分布。然后,根据它们在托盘上的位置和托盘的旋转角度,repositionTray() 函数计算它们在旋转托盘上出现的位置,然后它们各自被重新分配新的笛卡尔坐标 (x, y)。x 值为负的在左边,其余的在右边;y 值为负的在单位圆的底部,其他的在顶部。

一旦所有图片都放置在这个想象的圆上,它们将在 getRankingIndices() 函数中使用冒泡排序进行排名,该函数返回一个整数数组。这些整数对应于从后到前排名的数组元素(图片)的索引。

我们来看看这个著名函数中实际将图片重新定位到屏幕上的部分,这是在我们已经获得排名索引之后。

for (int intPicCounter = 0; 
           intPicCounter < 
           intRankingIndices.Length; 
           intPicCounter++)
{
    /// -   set their size accordingly (back is smaller, front is bigger) 
    double dblSizeAttenuationFactor; /// -1 is at the front, 1 is at the back
    double dblDistanceToFront = 1
                            + udrPicBoxTrayElements[
                                  intRankingIndices[intPicCounter]
                                                   ].trayLoc.y;
    // dblDistanceToFront ranges from 0(front) to 2(back)
    double dblFactorRange = 1 - dblMinSizeFraction;
    dblSizeAttenuationFactor = 1 
                           - dblFactorRange * (dblDistanceToFront / 2);
    resizePic(ref udrPicBoxTrayElements[
                      intRankingIndices[intPicCounter]
                                     ],
            attenuateUdtCartesian(udrMaxSize, dblSizeAttenuationFactor));

    /// -   place them on the form (left,top)
    ///  - center the x value of pic loc on circle Pt, 
    ///                 place top of pic on circle Pt
    udrCirclePt.x = udrCenter.x 
                + dblCircleRadius * Math.Cos(udrPicBoxTrayElements[
                                        intRankingIndices[intPicCounter]
                                                                  ]
                             .trayLoc.angle + dblCurrentAngle - Math.PI / 2);
    udrCirclePt.y = udrCenter.y 
                - dblCircleRadius * Math.Sin(udrPicBoxTrayElements[
                                        intRankingIndices[intPicCounter]
                                                                  ]
                             .trayLoc.angle + dblCurrentAngle - Math.PI / 2);
    udrPicBoxTrayElements[intRankingIndices[intPicCounter]].picBox.Left 
     = (int)(udrCirclePt.x - udrPicBoxTrayElements[
                                     intRankingIndices[intPicCounter]
                                                  ].picBox.Width / 2);
    udrPicBoxTrayElements[intRankingIndices[intPicCounter]].picBox.Top 
     = (int)(udrCirclePt.y);
    
    /// -   bring to front (will be covered by those that follow)
    udrPicBoxTrayElements[intRankingIndices[intPicCounter]].picBox.BringToFront();
}

我们已经知道以什么顺序将图片放到屏幕上,以便后面的图片出现在前面的图片后面。而且,我们已经知道它们在单位圆上的位置,所以找到屏幕上的相应位置并不太难。我们需要担心的是图片的大小,这取决于它离前面有多近以及图片的最大/最小尺寸是多少。

在我们的单位圆上,圆底部的图像位于或接近 y=-1,因此我们可以在计算 dblDistanceToFront 并将 1 添加到 y 值时利用此知识。这给出了该图像到前面的距离,范围在 0 到 2 之间,其中 0 离前面最近,2“单位”离前面的图像在最后面。dblMinSizeFraction 变量用于确定后面图像(较小)相对于前面图像最大尺寸的大小。我们使用到前面的距离和 factorRange 来“衰减”图像到适当的大小。由于每个图像不一定与其他图像具有相同的形状或大小,并且托盘需要适应所有形状和大小,因此此程序使用图像可以适应的最大值。在此限制下,函数 resizePic() 使用我们之前记录的图像宽高比,并在不扭曲其形状的情况下将图像放入允许的空间内。

接下来,我们需要将图像放置在屏幕上。为此,想象一下您正在将图钉穿过图片顶部边缘的中间,插入到旋转托盘上的某个点。您需要稍微复习一下三角学,并记住 Cos(theta) 是单位圆上角度 theta 处的 x 值,而 Sin(theta) 是同一单位圆上角度 theta 处的 y 值。您在计算这些值时要记住,这里的角度 theta 是图片在托盘上的位置加上托盘的旋转角度,再加上一个额外的 Pi/2 以求好运(实际上,这个额外的 Pi/2 是为了美观而将零角度放在屏幕底部)。然后,将这些值乘以输出圆的半径,并将它们添加到该圆的中心。

困惑吗?别担心。你有一个中心位置。你有一个旋转的托盘,托盘上有一些点;其余的更多是数学而不是编程,如果你需要,就拿出纸笔涂鸦一段时间,以便更好地理解它。

然而,有点问题的是,你的高中数学老师教你的单位圆与你的计算器使用的单位圆相同,也与你的电脑 CPU 知道的单位圆相同。**但是**,你的电脑屏幕有一个不同的老师,它有点困惑。所以,你需要记住你的屏幕的 Y 轴是颠倒的。这很不幸,但我们都必须接受。在将这些图片放到屏幕上,以及在进入有趣的计算机图形世界时计算上下位置时,你必须考虑到这一点。

我们正在从后到前遍历所有图像,所以当我们将图像放置在屏幕上时,我们必须通过调用 PictureBoxBrintToFront() 方法来确保它出现在之前放置的其他图像的前面。这就像你在桌子上丢图片。你最先丢的图片最终会被后面丢的图片覆盖,直到它们都在桌子上。这给了你深度错觉。较小的图片在后面,较大的图片在前面。

瞧,这已经比文艺复兴前更好了,而且你还没有弄脏你的手指。

历史

  • 更新:2009 年 8 月 13 日
    • 当时我不知道如何为一堆动态生成的 pictureBoxes 实现 mouseMove/mouseClick 事件,现在我知道了,不再需要使用像麻烦的“transparentLabel”这样有问题的方法进行交互。所以我删除了 HTML 中对这个现在无用标签的引用,并附上了编辑过的文章以及文章中出现的两个压缩文件,以便进行更新。
  • 更新:2009 年 11 月 15 日
    • 更新了源代码和演示
© . All rights reserved.