I\'m trying to populate a listview from another class, but I\'m geting this error: \" Cross-thread operation not valid: Control \'listView1\' accessed from a thread other than t
You are trying to update the GUI thread from you background thread. You will need to use Invoke on the control you would like to update. You can check the InvokeRequired
property on that control to see whether or not you will need to use Invoke
to update the control
A neat trick to avoid duplicate code or malfunction when a function is called both from UI thread and other threads is:
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
void AddItems( string[] items )
{
if(InvokeRequired)
{
Invoke((MethodInvoker) delegate { this.AddItems(items); });
return;
}
ListViewItem[] range = (items.Select<string, ListViewItem>(item => new ListViewItem(item))).ToArray();
listView1.Items.AddRange(range);
}
}
}
The first time the function enters in another thread, invoke is called and the function simply calls itself again, this time in the right thread context. The actual work is then written down only once after the if() block.
Usually I'm doing it like this:
using System;
using System.Windows.Forms;
namespace TestWinFormsThreding
{
class TestFormControlHelper
{
delegate void UniversalVoidDelegate();
/// <summary>
/// Call form control action from different thread
/// </summary>
public static void ControlInvoke(Control control, Action function)
{
if (control.IsDisposed || control.Disposing)
return;
if (control.InvokeRequired)
{
control.Invoke(new UniversalVoidDelegate(() => ControlInvoke(control, function)));
return;
}
function();
}
}
public partial class TestMainForm : Form
{
// ...
// This will be called from thread not the same as MainForm thread
private void TestFunction()
{
TestFormCotrolHelper.ControlInvoke(listView1, () => listView1.Items.Add("Test"));
}
//...
}
}
This is a reasonable thing to do, because often in application you want ListView updates etc to go on without holding up your code.
I have done the message systems between user controls containing the control/s I want to update in the back ground and it can get quite messy because you end up having to message/event for a lot more than just the fill/updates, messy code is buggy code, so I tried other ways.
There is a nice neat way, the slow part of the ListView fill/updates is generally in the creation of the ListViewItems, and you can fully prepare those in your own thread.
So now, for this sort of applications (With Fill or update ListView where I do not need to wait for it to be ready before my code can continue), My seperate thread Creates/prepares the ListViewItems then adding the prepared items to the ListView when the thread is done is very quick, so the final ListView update can be done on a user event hardly noticeable to the user. Add to this 'Only add those you can see' and it really is instantaneous. With a couple of extra rows so when scrolling starts you can add a couple more. (You might have noticed youtube/facebook/windows picture browser all do it this way). Since in our case we have already prepared the ListViewItems, adding them to the list is very straight forward.
A simple search here on SO would have brought up many results that tell you that it is not allowed to change a GUI control from a thread other than the thread which created the control (cross-thread GUI access).
To do so, everything related to updating the ListView
must be done using this.Invoke
or this.Dispatcher.Invoke
(in WPF).
EDIT
For example this thread here.
Sample code:
private delegate void MyDelegate(string s);
public void UpdateControl(Control targetControl, string text)
{
if (targetControl.InvokeRequired)
{
// THIS IS STILL THE IN THE CONTEXT OF THE THREAD
MyDelegate call = new MyDelegate(UpdateControl);
targetControl.Invoke(call, new object[] { text });
}
else
{
// do control stuff
// THIS IS IN THE CONTEXT OF THE UI THREAD
}
}