【问题标题】:Dynamically display an array of PictureBoxes - Performance issue动态显示图片框数组 - 性能问题
【发布时间】:2020-05-03 12:34:56
【问题描述】:

我想显示 MP3 库的每张专辑的封面,有点像 Itunes 所做的(在稍后阶段,我想单击这些封面中的任何一个以显示歌曲列表)。 我有一个带有panel1 面板的表单,这是我正在使用的循环:

 int i = 0;
        int perCol = 4;
        int disBetWeen = 15;
        int width = 250;
        int height = 250;
        foreach(var alb in mp2)
        {
                myPicBox.Add(new PictureBox());
                myPicBox[i].SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage;
                myPicBox[i].Location = new System.Drawing.Point(disBetWeen + (disBetWeen * (i % perCol) +(width * (i % perCol))), 
                    disBetWeen + (disBetWeen * (i / perCol))+ (height * (i / perCol))); 
                myPicBox[i].Name = "pictureBox" + i;
                myPicBox[i].Size = new System.Drawing.Size(width, height);
                myPicBox[i].ImageLocation = @"C:/Users/Utilisateur/Music/label.jpg";
                panel1.Controls.Add(myPicBox[i]);
                i++;
        }

为方便起见,我对每个图片框使用相同的图片,但最终我会使用嵌入在每个 mp3 文件中的封面。

图书馆的摘要(大约 50 个)可以正常工作,但我有数千张专辑。我尝试过,正如预期的那样,加载需要很长时间,之后我无法真正滚动。

有没有办法只加载显示的内容?然后是如何评估滚动条显示的内容。

谢谢

【问题讨论】:

  • 由于所有图像都是 250x250,您可以将它们加载到 ImageList 中并在 ListView 中显示它们。比创建大量控件要好得多..
  • var folders = getFolders(500); foreach (string fn in folders) { ilFolders.Images.Add(fn, Image.FromFile(fn)); } int i = 0; foreach (string k in ilFolders.Images.Keys) { var lvi = new ListViewItem(k, i); lv_folders.Items.Add(lvi); i++; } - 例如加载10 秒内覆盖 500 个封面。滚动没有任何问题。加载更多图像需要某种缓存/分页方案和/后台加载任务。
  • 分页演示见here
  • 感谢 TaW,我尝试了您的解决方案并且它有效,但我认为下面的答案为表单的组织提供了更大的灵活性。还是谢谢!

标签: c#


【解决方案1】:

Winforms 确实不适合这种事情...使用标准控件,您可能需要预先配置所有图像框并在图像可见时加载图像,或者管理一些溢出占位符适当的长度,以便滚动条起作用。

假设 Winforms 是您唯一的选择,我建议您考虑创建一个带有滚动条的自定义控件并手动驱动 OnPaint 事件。

这将允许您将图像缓存保留在内存中以绘制当前视图 [以及两侧的一些],同时让您完全控制它们何时加载/卸载 [嗯,就像您一样“完全”可以使用托管语言 - 您可能仍需要调整垃圾收集]

进入一些细节......

创建一个新控件

namespace SO61574511 {
    // Let's inherit from Panel so we can take advantage of scrolling for free
    public class ImageScroller : Panel {
        // Some numbers to allow us to calculate layout
        private const int BitmapWidth = 100;
        private const int BitmapSpacing = 10;

        // imageCache will keep the images in memory. Ideally we should unload images we're not using, but that's a problem for the reader
        private Bitmap[] imageCache;

        public ImageScroller() {
            //How many images to put in the cache? If you don't know up-front, use a list instead of an array
            imageCache = new Bitmap[100];
            //Take advantage of Winforms scrolling
            this.AutoScroll = true;
            this.AutoScrollMinSize = new Size((BitmapWidth + BitmapSpacing) * imageCache.Length, this.Height);

        }

        protected override void OnPaint(PaintEventArgs e) {
            // Let Winforms paint its bits (like the scroll bar)
            base.OnPaint(e);
            // Translate whatever _we_ paint by the position of the scrollbar
            e.Graphics.TranslateTransform(this.AutoScrollPosition.X,
                           this.AutoScrollPosition.Y);

            // Use this to decide which images are out of sight and can be unloaded
            var current_scroll_position = this.HorizontalScroll.Value;

            // Loop through the images you want to show (probably not all of them, just those close to the view area)
            for (int i = 0; i < imageCache.Length; i++) {
                e.Graphics.DrawImage(GetImage(i), new PointF(i * (BitmapSpacing + BitmapWidth), 0));
            }

        }

        //You won't need a random, just for my demo colours below
        private Random rnd = new Random();

        private Bitmap GetImage(int id) {
            // This method is responsible for getting an image.
            // If it's already in the cache, use it, otherwise load it
            if (imageCache[id] == null) {
                //Do something here to load an image into the cache
                imageCache[id] = new Bitmap(100, 100);

                // For demo purposes, I'll flood fill a random colour
                using (var gfx = Graphics.FromImage(imageCache[id])) {
                    gfx.Clear(Color.FromArgb(255, rnd.Next(0, 255), rnd.Next(0, 255), rnd.Next(0, 255)));
                }
            }
            return imageCache[id];
        }

    }
}

并将其加载到您的表单中,停靠以填满屏幕......

    public Form1() {
        InitializeComponent();
        this.Controls.Add(new ImageScroller {
            Dock = DockStyle.Fill
        });
    }

你可以在这里看到它的实际效果:https://www.youtube.com/watch?v=ftr3v6pLnqA(请原谅鼠标轨迹,我在窗口外捕获了区域)

【讨论】:

  • 好吧,不幸的是,这听起来超出了我的能力,但还是谢谢 :)
  • 我添加了一个例子
  • 哦,从您上次的评论...使用滚动位置和控件尺寸,您应该能够计算出可见的矩形。从那里,计算出光标下的图像是简单的数学[假设位图为 100 像素,间距为 10 像素]...Math.Floor(x/110) + Math.Floor(y/110) * numImagesPerRow。 (已经很晚了,我睡眠不足,所以仔细检查一下,但你明白了......第一部分将随着鼠标向左/向右移动而增加,后者每行一次。我用过@987654329 @ 解释发生了什么,但你可以直接转换为 int,它在这里会产生相同的效果。
  • 另请查看this.AutoScrollPosition - 它获取可视区域左上角的相对 X/Y 位置。这与控制/鼠标位置一起应该是您需要的。最后一个建议...在您实际绘制图像的位置 (e.Graphics.DrawImage(GetImage(i),.....),您还可以执行其他绘图任务。例如,您可以通过在图像之前绘制一个黑色矩形偏移来添加边框(矩形)或阴影)。查看docs.microsoft.com/en-us/dotnet/api/… 了解更多信息
  • 基本,我成功了 :) 你可以在这里看到视频:link。我调查了处置过程,因为如您所见,它不是完全流动的,我想知道它是否是内存问题。我在那里发布了一个问题:link 再次感谢所有帮助:) :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多