3

I want to get a control's property from a BackgroundWorker in my form:

foreach (ListViewItem i in ListView.CheckedItems) { //error: Cross-thread operation not valid: Control 'ListView' accessed from a thread other than the thread it was created on.
    //do something with i
}

Can anyone suggest the simplest and easiest method to do this?

Elmo
  • 6,409
  • 16
  • 72
  • 140

2 Answers2

2

Let me take another stab at this...

1.) Drag a ListView onto the Form

2.) Drag a BackgroundWorker onto the Form

3.) Create a method do iterate through the ListViewItem collection

private void LoopThroughListItems()
{
    foreach (ListViewItem i in listView1.CheckedItems)
        DoSomething(); 

}

4.) Add code to call LoopThroughListItems() inside the BackgroundWorker's DoWork Event

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

5.) In your Form Load - execute the code on the main thread (it works) then on the backgroundWorkder thread (it fails)

private void Form1_Load(object sender, EventArgs e)
{
    // Try it on the UI Thread - It works
    LoopThroughListItems();

    // Try it on a Background Thread - It fails
    backgroundWorker1.RunWorkerAsync();

}

6.) Modify your code to use IsInvokeRequired/Invoke

private void LoopThroughListItems()
{

    // InvokeRequired == True when executed by non-UI thread
    if (listView1.InvokeRequired)
    {
        // This will re-call LoopThroughListItems - on the UI Thread
        listView1.Invoke(new Action(LoopThroughListItems));
        return;
    }

    foreach (ListViewItem i in listView1.CheckedItems)
        DoSomething(); 
}

7.) Run the app again - now it works on the UI thread and the non-UI thread.

That solve the problem. The checking IsInvokeRequired/Invoking is a common pattern you'll get used to a lot (which is why it's included on all Controls). If you are doing it all over the place, you can do something clever and wrap it all up - as described here: Automating the InvokeRequired code pattern

Community
  • 1
  • 1
Rob P.
  • 14,921
  • 14
  • 73
  • 109
1

Try something like this:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

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

        private void OnDoWork(object sender, DoWorkEventArgs e)
        {
            foreach (ListViewItem i in GetItems(listView1))
            {
                DoSomething(i);
            }
        }

        private IEnumerable<ListViewItem> GetItems(ListView listView)
        {
            if (InvokeRequired)
            {
                var func = new Func<ListView, IEnumerable<ListViewItem>>(GetItems);
                return (IEnumerable<ListViewItem>)Invoke(func, new[] { listView });
            }
            // Create a defensive copy to avoid iterating outsite UI thread
            return listView.CheckedItems.OfType<ListViewItem>().ToList();
        }

        private void DoSomething(ListViewItem item)
        {
            if (InvokeRequired)
            {
                var action = new Action<ListViewItem>(DoSomething);
                Invoke(action, new[] { item });
                return;
            }
            // Do whatever you want with i
            item.Checked = false;
        }
    }
}

However, your question was really general. Maybe there would be a easier or better solution if you shared more details.

DavWEB
  • 420
  • 4
  • 14