0

I'm finalising the code to my thumbnail UserControl, one of it's requirements is to be able to load in up to 3,000 images at any one time. As you can imagine, without threading this would render the control all but useless in a live environment as it could take quite some considerable time to load them in and in this time the user is frozen out of any program which utilises this control.

I'm new to threading and what I have seen and tried require that I change the signature of GenerateThumbnails and also generate errors (Not included in this code).

What I would like to know is how would I go about implementing threading to allow user interaction whilst the images are loaded into the PictureBoxes.

The code for this control is as follows (I include it in it's entirety) - Updated with BackgroundWorker implemented.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace ImageViewerPlus
{

    public partial class Filmstrip : UserControl
    {

        //private BackgroundWorker bwAsyncWorker = new BackgroundWorker();

        public Filmstrip()
        {
            InitializeComponent();
            log = log + "Initialized Filmstrip Form: \r\n";
            //bwAsyncWorker.WorkerReportsProgress = true;
            //bwAsyncWorker.WorkerSupportsCancellation = true;
            //bwAsyncWorker.ProgressChanged += new ProgressChangedEventHandler(bwAsync_ProgressChanged);
            //bwAsyncWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bwAsync_RunWorkerCompleted);
            //bwAsyncWorker.DoWork += new DoWorkEventHandler(bwAsync_DoWork);

            toolStripProgressBar1.Minimum = 0;
            toolStripProgressBar1.Maximum = 100;
        }

        protected override void OnPaint(PaintEventArgs pe)
        {
            SolidBrush b = new SolidBrush(this.BackColor);
            pe.Graphics.FillRectangle(b, this.ClientRectangle);
        }

        public string log;
        private string selectedImagePath;
        private List<PictureBox> pictureBoxes;
        private List<string> imagePaths;
        private int imageCount;
        private int selectedImageIndex;
        private int pictureBoxIndex;
        private int oldSelectedImageIndex;


        public SelectedImageDelegate SelectedImageCallback;

        private static int THUMBNAIL_SIZE = 100;

        private List<PictureBox> PictureBoxes
        {
            set
            {
                pictureBoxes = value;
            }
            get
            {
                return pictureBoxes;
            }
        }

        public List<string> ImagePaths
        {
            set
            {
                imagePaths = value;
            }
            get
            {
                return imagePaths;
            }
        }

        private int ImageCount
        {
            set
            {
                imageCount = value;
            }
            get
            {
                return imageCount;
            }
        }

        public int SelectedImageIndex
        {
            set
            {
                selectedImageIndex = value;
            }
            get
            {
                return selectedImageIndex;
            }
        }

        public string SelectedImagePath
        {
            set
            {
                selectedImagePath = value;
            }
            get
            {
                return selectedImagePath;
            }
        }

        private int PictureBoxIndex
        {
            set
            {
                pictureBoxIndex = value;
            }
            get
            {
                return pictureBoxIndex;
            }
        }

        public void GenerateThumbnails(List<string> images)
        {
            log = log + "GenerateThumbnails() Entered: \r\n";
            ImagePaths = images;
            ImageCount = ImagePaths.Count;
            int pictureBoxTopInterval = THUMBNAIL_SIZE + 4;
            int pictureBoxLeftInterval = THUMBNAIL_SIZE + 4;
            int thumbnailColumns = this.panelDoubleBuffered1.Width / (THUMBNAIL_SIZE);
            int remainder;
            int thumbnailRows = Math.DivRem(ImageCount, thumbnailColumns, out remainder);
            int thumbnailBreak = 0;

            if (remainder > 0)
            {
                thumbnailRows = thumbnailRows + 1;
            }

            PictureBoxes = new List<PictureBox>();
            pictureBoxIndex = 0;

            log = log + "Generating PictureBox array: \r\n";
            for (int i = 0; i < thumbnailRows; i = i + 1)
            {
                log = log + "Row " + i.ToString() + ", Column ";
                for (int j = 0; j < thumbnailColumns; j = j + 1)
                {
                    log = log + j.ToString() + ", ";
                    if (thumbnailBreak == ImageCount)
                    {
                        break;
                    }
                    else
                    {
                        PictureBox nextPictureBox = new PictureBox();

                        PictureBoxes.Add(nextPictureBox);

                        PictureBoxes[PictureBoxIndex].ImageLocation = ImagePaths[PictureBoxIndex];
                        PictureBoxes[PictureBoxIndex].Parent = panelDoubleBuffered1;
                        PictureBoxes[PictureBoxIndex].Width = THUMBNAIL_SIZE;
                        PictureBoxes[PictureBoxIndex].Height = THUMBNAIL_SIZE;
                        PictureBoxes[PictureBoxIndex].Tag = pictureBoxes[PictureBoxIndex].ImageLocation;
                        PictureBoxes[PictureBoxIndex].Top = 3 + (i * pictureBoxTopInterval);
                        PictureBoxes[PictureBoxIndex].Left = 3 + (j * pictureBoxLeftInterval);
                        PictureBoxes[PictureBoxIndex].Show();
                        PictureBoxes[PictureBoxIndex].Click += new EventHandler(OnButtonClick);
                        PictureBoxIndex = PictureBoxIndex + 1;
                        this.Refresh();
                        thumbnailBreak = thumbnailBreak + 1;
                    }
                }
                log = log + ": Initialised\r\n";
            }

            PictureBoxIndex = 0;
            log = log + "if (bwAsyncWorker.IsBusy) - ";
            if (bwAsyncWorker.IsBusy)
            {
                log = log + "bgw is busy: \r\n";
                //toolStripStatusLabel1.Text = "Cancelling...";
                //bwAsyncWorker.CancelAsync();
            }
            else
            {
                toolStripStatusLabel1.Text = "Loading...";
                log = log + "bwAsyncWorker.RunWorkerAsync(); - ";
                bwAsyncWorker.RunWorkerAsync();
            }
        }

        private void bwAsync_DoWork(object sender, DoWorkEventArgs e)
        {
            log = log + "Executed:\r\n";
            BackgroundWorker bwAsync = sender as BackgroundWorker;

            for (int i = 0; i < ImageCount; i = i + 1)
            {
                try
                {
                    log = log + "bwAsync.ReportProgress(Convert.ToInt32(i * (100.0 / ImageCount))); - ";
                    bwAsync.ReportProgress(Convert.ToInt32(i * (100.0 / ImageCount)));
                    log = log + "Executed:\r\n";

                    log = log + "ProgressBar updated to: " + (Convert.ToInt32(i * (100.0 / ImageCount))) + "\r\n";

                    log = log + "Image " + i.ToString();
                    PictureBoxes[i].Load();
                    log = log + " Has Loaded\r\n";

                    log = log + "SetImage(PictureBoxes[i]); - ";
                    SetImage(PictureBoxes[i]);
                    log = log + "Executed:\r\n";

                    if (bwAsync.CancellationPending)
                    {
                        log = log + "e.Cancel = true; - ";
                        e.Cancel = true;
                        log = log + "Executed\r\n";

                        log = log + "return;\r\n";
                        return;
                    }

                }
                catch
                {
                }
            }
        }

        private void bwAsync_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                log = log + "RunWorkerComplete: " + e.Error.Message + "\r\n";
                MessageBox.Show(e.Error.Message);
                return;
            }

            if (e.Cancelled)
            {
                log = log + "RunWorkerComplete: Cancelled...\r\n";
                toolStripStatusLabel1.Text = "Cancelled...";
            }
            else
            {
                log = log + "RunWorkerComplete: Completed...\r\n";
                toolStripStatusLabel1.Text = "Completed...";
                toolStripProgressBar1.Value = 100;
            }
        }

        private void bwAsync_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            log = log + " + "; // shows that ProgressChanged was entered.
            toolStripProgressBar1.Value = e.ProgressPercentage;
            Application.DoEvents();
        }

        public void OnButtonClick(object sender, EventArgs e)
        {
            log = log + "Selected image changed to: " + SelectedImageIndex.ToString() + ": \r\n";
            oldSelectedImageIndex = SelectedImageIndex;
            SelectedImagePath = ((PictureBox)sender).Tag.ToString();
            SelectedImageCallback(SelectedImagePath);
            SelectedImageIndex = ImagePaths.IndexOf(SelectedImagePath);
            oldSelectedImageIndex = SelectedImageIndex;
            Refresh();

            Graphics dc = this.panelDoubleBuffered1.CreateGraphics();
            Pen Redpen = new Pen(Color.Red, 2);
            dc.DrawRectangle(Redpen, PictureBoxes[SelectedImageIndex].Left - 2, PictureBoxes[SelectedImageIndex].Top - 2, 104, 104);
        }

        public static Bitmap ConvertToRGB(Bitmap original)
        {
            Bitmap newImage = new Bitmap(original.Width, original.Height, PixelFormat.Format32bppArgb);
            newImage.SetResolution(original.HorizontalResolution, original.VerticalResolution);
            using (Graphics g = Graphics.FromImage(newImage))
            {
                g.DrawImageUnscaled(original, 0, 0);
            }
            return newImage;
        }

        //Generate new image dimensions
        public Size GenerateImageDimensions(int currW, int currH, int destW, int destH)
        {
            //double to hold the final multiplier to use when scaling the image
            double multiplier = 0;

            //string for holding layout
            string layout;

            //determine if it's Portrait or Landscape
            if (currH > currW) layout = "portrait";
            else layout = "landscape";

            switch (layout.ToLower())
            {
                case "portrait":
                    //calculate multiplier on heights
                    if (destH > destW)
                    {
                        log = log + "multiplier = (double)destW / (double)currW;";
                        multiplier = (double)destW / (double)currW;
                        log = log + "Executed\r\n";
                    }

                    else
                    {
                        log = log + "multiplier = (double)destH / (double)currH;";
                        multiplier = (double)destH / (double)currH;
                        log = log + "Executed\r\n";
                    }
                    break;
                case "landscape":
                    //calculate multiplier on widths
                    if (destH > destW)
                    {
                        log = log + "multiplier = (double)destW / (double)currW;";
                        multiplier = (double)destW / (double)currW;
                        log = log + "Executed\r\n";
                    }

                    else
                    {
                        log = log + "multiplier = (double)destH / (double)currH;";
                        multiplier = (double)destH / (double)currH;
                        log = log + "Executed\r\n";
                    }
                    break;
            }

            //return the new image dimensions
            try
            {
                return new Size((int)(currW * multiplier), (int)(currH * multiplier));
            }
            catch (System.Exception e)
            {
                log = log + "Calling from GenerateImageDimensions(): " + e.Message;
                return new Size((int)(currW * multiplier), (int)(currH * multiplier));
            }

        }

        //Resize the image
        public void SetImage(PictureBox pb)
        {
            try
            {
                //create a temp image
                log = log + "Image imgtmp = pb.Image; - ";
                Image imgtmp = pb.Image;
                log = log + "Executed\r\n";
                //calculate the size of the image
                log = log + "Size imgSize = GenerateImageDimensions(imgtmp.Width, imgtmp.Height, pb.Width, pb.Height); - ";
                Size imgSize = GenerateImageDimensions(imgtmp.Width, imgtmp.Height, pb.Width, pb.Height);
                log = log + "Executed\r\n";

                //create a new Bitmap with the proper dimensions
                log = log + "Bitmap finalImg = new Bitmap(imgtmp, imgSize.Width, imgSize.Height);";
                Bitmap finalImg = new Bitmap(imgtmp, imgSize.Width, imgSize.Height);
                log = log + "Executed\r\n";

                log = log + "Bitmap img = ConvertToRGB(finalImg);";
                Bitmap img = ConvertToRGB(finalImg);
                log = log + "Executed\r\n";

                //create a new Graphics object from the image
                log = log + "Graphics gfx = Graphics.FromImage(img);";
                Graphics gfx = Graphics.FromImage(img);
                log = log + "Executed\r\n";

                //clean up the image (take care of any image loss from resizing)
                log = log + "gfx.InterpolationMode = InterpolationMode.HighQualityBicubic;";
                gfx.InterpolationMode = InterpolationMode.HighQualityBicubic;
                log = log + "Executed\r\n";

                //empty the PictureBox
                log = log + "pb.Image = null;";
                pb.Image = null;
                log = log + "Executed\r\n";

                //center the new image
                log = log + "pb.SizeMode = PictureBoxSizeMode.CenterImage;";
                pb.SizeMode = PictureBoxSizeMode.CenterImage;
                log = log + "Executed\r\n";

                //set the new image
                log = log + "pb.Image = finalImg;";
                pb.Image = finalImg;
                log = log + "Executed\r\n";
            }
            catch (System.Exception e)
            {
                log = log + "Calling from SetImage: - " + e.Message + "\r\n";
                MessageBox.Show(e.Message);
            }
        }
    }
}

Here is an image of the application, notice how some of the thumbnails have not been resized, I cannot fathom why at all.

Because of the spam prevention, I cannot post pictures yet, but you can find one here.

I can supply the log output too if required.

Dropjaw
  • 115
  • 5

1 Answers1

0

Use a BackgroundWorker thread.

private void bw_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker worker = sender as BackgroundWorker;

    for (int i = 1; (i <= 10); i++)
    {
        if ((worker.CancellationPending == true))
        {
            e.Cancel = true;
            break;
        }
        else
        {
            // **Perform your uploads here** and report progress.
            System.Threading.Thread.Sleep(500);
            worker.ReportProgress((i * 10));
        }
    }
}
Jake1164
  • 12,291
  • 6
  • 47
  • 64
  • Cheers for that, took me a while to implement it but I got it there, I just have some minor issues with it now. I had to syphon off the loading of the images into it's own for loop, separate from the PictureBoxes initialisations to avoid thread conflicts. The problem I have now is that however many pictureboxes are on screen when the images are loading, refresh and user input is frozen until they are loaded, so if there are 12 visible, there is no interaction until all 12 have loaded. – Dropjaw Jun 30 '12 at 07:56
  • What version of the framework are you using? if you are using 4.0 you could create tasks and use the parallel features of 4.0 to upload more at one time. – Jake1164 Jun 30 '12 at 11:03
  • I'm using VS2012 RC2 atm, so 4.5. I only have VSE otherwise so I'm making the most of it whilst it's still active ;) - I'll have to look into the parallel features, any resources you can recommend? – Dropjaw Jun 30 '12 at 16:23
  • Iv'e updated the code in the question, it now includes my implementation of the BackgrondWorker and also the thumbnail resizing routines. I now have issues with some of the images not resizing whilst their respective PictureBox is visible. Resizing the application so that the thumbnail control is not visible solves this issue, however, if the images are still loading and I expand the application, those images loading will once again have this problem. – Dropjaw Jun 30 '12 at 17:21