Search code examples
c#listviewbackgroundworkerimagelist

Loading ListView and ImageList while using a backgroundWorker


Loading the imagelist and the listview obviously causes the UI to hang a little. So I'd like to load the UI and let the user know we are working on things and let the backgroundWorker do it's thing. I tried on the formLoad to call the "backgroundWorker1.RunWorkerAsync();"

But I got "Cross-thread operation not valid: Control 'listView1' accessed from a thread other than the thread it was created on."

I poked around a bit and figured out that I need to change the code a bit. So this is what I have:

    private void WatermarkPicker_Load(object sender, EventArgs e)
    {
        backgroundWorker1.RunWorkerAsync();
    }


private void GetImages()
    {
        DirectoryInfo dir = new DirectoryInfo(@"c:\pics");
        this.listView1.View = View.LargeIcon;
        this.imageList1.ImageSize = new Size(100, 100);
        if (listView1.InvokeRequired)
        {
            listView1.Invoke(new MethodInvoker(
                () => this.listView1.LargeImageList = this.imageList1));
        }
        else
        {
            this.listView1.LargeImageList = this.imageList1;
        }
        int j = 0;
        foreach (FileInfo file in dir.GetFiles())
        {
            try
            {
                //this.imageList1.Images.Add(Image.FromFile(file.FullName));
                imageList1.Images.Add(file.Name, Image.FromFile(file.FullName));
                ListViewItem item = new ListViewItem(file.Name);
                item.Tag = file.Name;
                item.ImageIndex = j;
                this.listView1.Items.Add(item);
                j++;
            }
            catch
            {
                Console.WriteLine("This is not an image file");
            }
        }
    }

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        GetCurrentLogos();
        GetImages();
    }

The if (listView1.InvokeRequired) is satisfied, and runs the listView1.invoke. But nothing shows up in the listView in the UI. No errors though.


Solution

  • You should move all the code that tries to access the Listview AND the ImageList outside the DoWork event and use the ProgressChanged event raised inside the DoWork code.

    In this way the code inside the ProgressChanged event is executed in the correct UI thread (assuming that the BackgroundWorker is created in the UI thread).

    For example:

    private void WatermarkPicker_Load(object sender, EventArgs e)
    {
        listView1.View = View.LargeIcon;
        listView1.LargeImageList = imageList1;
    
        backgroundWorker1.WorkerReportsProgress = true;
        backgroundWorker1.ProgressChanged += backgroundWorker1_ProgressChanged;
        backgroundWorker1.DoWork += backgroundWorker1_DoWork;
        backgroundWorker1.RunWorkerAsync();
    }
    
    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        // Do not try to use in any way an object derived from Control
        // like the ListView when you are inside the DoWork method.....
        BackgroundWorker worker = sender as BackgroundWorker;
        DirectoryInfo dir = new DirectoryInfo(@"c:\pics");
        foreach (FileInfo file in dir.GetFiles())
        {
            // You can't use imageList here
            // imageList1.Images.Add(file.Name, Image.FromFile(file.FullName));
    
            // Raise the ProgressChanged event. 
            // The code there will execute in the UI Thread
            worker.ReportProgress(1, file);        
        }
    }
    
    private void backgroundWorker1_ProgressChanged(object sender,  ProgressChangedEventArgs e)
    {
        // This code executes in the UI thread, no problem to 
        // work with Controls like the ListView
        try
        {
            FileInfo file = e.UserState as FileInfo;
            imageList1.Images.Add(file.Name, Image.FromFile(file.FullName));
            ListViewItem item = new ListViewItem(file.Name);
            item.Tag = file.Name;
            item.ImageIndex = imageList1.Images.Count - 1;
            listView1.Items.Add(item);
         }
         catch(Exception ex)
         {
            ....
         }
    }