Reputation: 12599
Having an issue getting data from a form control from within a thread. I need to access the data, then modify it.
The following doesn't work I'm aware, but I used it as an example to see what I'm trying to do.
Thread t = new Thread(() => {
foreach (ListViewItem row in listView1.Items)
{
row.SubItems[0].Text = "Checking";
Thread.Sleep(2000);
}
});
t.Start();
I've read the MSDN documentation on making thread safe calls, but I can't seem to get access to the actual list view control. The examples I've seen use delegates to "update" controls, but I need access to the data in the controls before I update the data in them.
Edit:
I'd like to see an example, or a link to an example, detailing how to get access to the ListView1 form control in the foreach loop.
Upvotes: 1
Views: 1478
Reputation: 626
Method 1:
Use Invoke like Tigran describes.
For Winforms this would look like:
Thread t = new Thread(() =>
{
if (!Dispatcher.CurrentDispatcher.CheckAccess())
{
Dispatcher.CurrentDispatcher.BeginInvoke(
new Action(() =>
{
foreach (ListViewItem row in listView1.Items)
{
row.SubItems[0].Text = "Checking";
Thread.Sleep(2000);
}
}),
DispatcherPriority.ApplicationIdle,
null);
}
else
{
foreach (ListViewItem row in listView1.Items)
{
row.SubItems[0].Text = "Checking";
Thread.Sleep(2000);
}
}
});
t.Start();
The CheckAccess() Call returns true if called from the UI-Thread otherwise false.
The Dispatcher Class is located in the "System.Windows.Threading" Namespace in the "WindowsBase" NET. Assembly
Dispatcher info copied from: https://stackoverflow.com/a/4429009/1469035
Edit: Changed code to WinForms. Edit: Code Fixed.
Method 2:
Use a Callback:
Untested Code:
public partial class Form1 : Form
{
private delegate void SetCallback(ListViewItem row, string text);
public Form1()
{
InitializeComponent();
}
private void SomeMethod()
{
Thread t = new Thread(() =>
{
foreach (ListViewItem row in listView1.Items)
{
if (listView1.InvokeRequired)
{
SetCallback d = new SetCallback(SetText);
this.Invoke(d, new object[] { row, "Checking" });
}
Thread.Sleep(2000);
}
});
t.Start();
}
private void SetText(ListViewItem row, string text)
{
row.SubItems[0].Text = text;
}
}
AFAIK readonly Access to Controls from Threads other than the UI-Thread is allowed in Winforms. So you can check any Control-Property you want and pass the required information to the Delegate.
And even if Reading doents work that way, you can just make another Delegate that has a return value. The Invoke() Method returns an object:
Similar to this:
private delegate object GetCallback(ListViewItem row);
private object o;
...
GetCallback g = new GetCallback(GetText);
o = this.Invoke(g, new object[] { row });
private string GetText(ListViewItem row)
{
return row.SubItems[0].Text;
}
Derived From: Link
Upvotes: 0
Reputation: 1508
You can't do what you want to do. All accesses and updates to UI must go in UI thread. It is mandatory. What you can do is writing your raw data into cache on UI then processing your cache and callbacks to UI after all processings are finished.
public class CacheData {
private object row;
public CacheData(object row)
{
//initialization
}
public static ProcessedData ProcessData(List<CacheData> dataToProcess)
{
return new ProcessedData();
}
}
public class ProcessedData { }
private void AccessControl()
{
ListView list = new ListView();
List<CacheData> cache = new List<CacheData>();
//Filling the cache on UI
foreach (var row in list.Items)
{
cache.Add(new CacheData(row));
}
//Process result async and then invoke on UI back
System.ComponentModel.BackgroundWorker bg = new System.ComponentModel.BackgroundWorker();
bg.DoWork += (sender,e) => {
e.Result = CacheData.ProcessData(cache);
};
bg.RunWorkerCompleted += (sender, e) => {
//If you have started your bg from UI result will be invoked in UI automatically.
//Otherwise you should invoke it manually.
list.Dispatcher.Invoke((Action) delegate {
//pass e.result to control here)
},null);
};
bg.RunWorkerAsync();
}
Upvotes: 0
Reputation: 43330
The (quickly written) example I was talking about, this assumes that you do not need to really use the controls, I included a function that is based off tigran's link
Thread t = new Thread(() => UpdateText(listBox1.Items));
t.Start();
private void UpdateText(ListBox.ObjectCollection items)
{
foreach (var item in items)
{
SetText(item.ToString());
Thread.Sleep(1000);
}
}
Upvotes: 1
Reputation: 62265
You need to use Invoke pattern, in order to be able to access any UI element or its properties from the thread other then main UI thread. All UI controls on windows allways run on the main thread, to handle message chain correctly between OS and UI presented on the screen.
Upvotes: 3