C# Where to use threading while loading multiple images into multiple PictureBoxes
<p>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.</p>

<p>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).</p>

<p>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.</p>

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

<pre><code>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&lt;PictureBox&gt; pictureBoxes;
        private List&lt;string&gt; imagePaths;
        private int imageCount;
        private int selectedImageIndex;
        private int pictureBoxIndex;
        private int oldSelectedImageIndex;


        public SelectedImageDelegate SelectedImageCallback;

        private static int THUMBNAIL_SIZE = 100;

        private List&lt;PictureBox&gt; PictureBoxes
        {
            set
            {
                pictureBoxes = value;
            }
            get
            {
                return pictureBoxes;
            }
        }

        public List&lt;string&gt; 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&lt;string&gt; 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 &gt; 0)
            {
                thumbnailRows = thumbnailRows + 1;
            }

            PictureBoxes = new List&lt;PictureBox&gt;();
            pictureBoxIndex = 0;

            log = log + "Generating PictureBox array: \r\n";
            for (int i = 0; i &lt; thumbnailRows; i = i + 1)
            {
                log = log + "Row " + i.ToString() + ", Column ";
                for (int j = 0; j &lt; 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 &lt; 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 &gt; currW) layout = "portrait";
            else layout = "landscape";

            switch (layout.ToLower())
            {
                case "portrait":
                    //calculate multiplier on heights
                    if (destH &gt; 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 &gt; 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);
            }
        }
    }
}
</code></pre>

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

<p>Because of the spam prevention, I cannot post pictures yet, but you can find one <a href="http://spacemarines.co.uk/programming/CSharp/PVPlus.jpg" rel="nofollow">here</a>.</p>

<p>I can supply the log output too if required.</p>
