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?
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
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.
Related
I have a WPF view with grid that have two elements in it, RichText and Progressbar.
When I'm loading a lot of text to RichText I want to show a loading process (just an animation) to the user.
The main idea to hide Richtext control, show Progressbar, start load text, when it finish show RichText again.
The problem is that while I'm updating RichText control I'm blocking UI and Progressbar is freezed.
Is there a way to update Progressbar from another thread, maybe some proxyhost?
Thank you.
Is there a way to update Progressbar from another thread
Short answer: No. A control can only be updated on the thread on which it was originally created.
What you can do is to display the ProgressBar in another window that runs on another thread and then close this window when the RichTextBox on the original thread has been updated.
You can add this to your usings:
using System.Windows.Threading;
For .NET 5/6, that is enough. For .NET framework you must also add a reference to System.Windows.Presentation.dll.
And this type of code will work fine:
private void Button_Click(object sender, RoutedEventArgs e)
{
// here we're in UI thread
var max = 100;
pg.Maximum = max; // pg is a progress bar
Task.Run(() =>
{
// here we're in another thread
for (var i = 0; i < max; i++)
{
Thread.Sleep(100);
// this needs the System.Windows.Threading using to support lambda expressions
Dispatcher.BeginInvoke(() =>
{
// this will run in UI thread
pg.Value = i;
});
}
});
}
Create a method on your form to update the element you want to update and use invoke to run it from your thread
like that:
private void Form1_Load(object sender, EventArgs e)
{
Thread _thread = new Thread(() =>
{
//do some work
upDateUiElements();//pass any parameters you want
});
_thread.Start();
}
public void upDateUiElements()//input any parameters you need
{
BeginInvoke(new MethodInvoker(() =>
{
//update ui elements
}));
}
If you need to invoke it from a different class you can pass your form as an object and then access the method through that object
Like that:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
OtherClass _class = new OtherClass(this);
_class.runthread();
}
public void upDateUiElements()//input any parameters you need
{
BeginInvoke(new MethodInvoker(() =>
{
//update ui elements
}));
}
}
class OtherClass
{
private Form1 _accessForm1;
public OtherClass(Form1 accessform1)
{
_accessForm1 = accessform1;
}
public void runthread()
{
Thread _thread = new Thread(() =>
{
//do some work
_accessForm1.upDateUiElements();
});
_thread.Start();
}
}
I have a gui form that displays a treeview. I display an alert form that uses a marquee progress bar while a backgroundworker builds the treeview. The issue I have is that the marquee does not display and the progresschanged routine does not fire until control returns to the gui, not while the dowork is running.
Can anyone please assist as to whether I have coded this incorrectly or have misunderstood how it should work? Thanks.
setup:
alert = new AlertForm();
alert.Message = string.Format("Building folder tree ({0}), please wait.", Global.RootDir);
// event handler for the Cancel button in AlertForm
alert.Cancelled += new EventHandler<EventArgs>(btnExit_Click);
alert.Show();
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.WorkerSupportsCancellation = true;
backgroundWorker1.DoWork += backgroundWorker1_DoWork;
backgroundWorker1.RunWorkerCompleted += backgroundWorker1_RunWorkerCompleted;
backgroundWorker1.ProgressChanged += backgroundWorker1_ProgressChanged;
backgroundWorker1.RunWorkerAsync();
background worker code (debug steps onto .ReportProgress but not into backgroundWorker1_ProgressChanged) :
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
backgroundWorker1.ReportProgress(1);
Invoke(new MethodInvoker(() => CreateTreeView(treeView1)));
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
alert.Refresh();
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
alert.Close();
if (treeView1.Nodes.Count > 0)
{
treeView1.ExpandAll();
treeView1.TopNode = treeView1.Nodes[0];
}
}
what is displayed:
worker running - no marquee in gui
worker complete - marquee displays in gui
========================================================================
Based on the comments below I have ended up with creating a new treeview object then cloning that into the gui object.
TreeView treeView = new TreeView();
CreateTreeView(treeView);
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
if (Global.Error)
{
_alert.Close();
string errorMessage = Global.ErrorMessage;
MessageBox.Show(errorMessage, #"Error occurred", MessageBoxButtons.OK);
Global.Error = false;
}
}
else
{
TreeView treeView = (TreeView)e.Result;
CopyTreeNodes(treeView, treeView1);
if (treeView1.Nodes.Count > 0)
{
treeView1.ExpandAll();
treeView1.TopNode = treeView1.Nodes[0];
}
_alert.Close();
treeView = null;
}
}
public void CopyTreeNodes(TreeView treeViewSource, TreeView treeViewTarget)
{
foreach (TreeNode node in treeViewSource.Nodes)
{
TreeNode treeNode = new TreeNode(node.Text, node.ImageIndex, node.SelectedImageIndex);
CopyNodeChildren(treeNode, node);
treeViewTarget.Nodes.Add((TreeNode)node.Clone());
}
}
public void CopyNodeChildren(TreeNode parent, TreeNode original)
{
foreach (TreeNode tn in original.Nodes)
{
TreeNode treeNode = new TreeNode(tn.Text, tn.ImageIndex, tn.SelectedImageIndex);
parent.Nodes.Add((TreeNode)tn.Clone());
CopyNodeChildren(treeNode, tn);
}
}
I am making assumptions regarding the version of c# you are using, but if you are using 4.5 or later then you don't need a BackgroundWorker if you can work async/await patterns into your code. The downside is that working async/await into an existing code base can sometimes be challenging.
Form1 kicks things off by showing the alert form. The alert form has a CancellationTokenSource that can be used to cancel the operation.
Then CallTreeView is run asynchronously. It will abort if you click the cancel button. The progress bar is simply set to marquee style, you don't need to increment it or do anything with it, it will simply pulse the entire time the alert form is open.
Form1:
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private async void button1_Click(object sender, EventArgs e)
{
AlertForm af = new AlertForm();
af.Show();
//I assume CallTreeView is not implemented in your form's code behind,
//if it is you do not need to pass it as a parameter
await Task.Run(async () => await CallTreeView(treeView1, af.Cts.Token));
af.Close();
}
private async Task CallTreeView(TreeView tv, CancellationToken token)
{
for (int i = 0; i < 10; i++)
{
if (token.IsCancellationRequested)
{
//clean up whatever you need to
return;
}
else
{
await Task.Delay(500); //just simulate doing something
//add nodes...
}
}
}
}
}
Alert Form:
using System;
using System.Threading;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class AlertForm : Form
{
public CancellationTokenSource Cts { get; set; }
public AlertForm()
{
InitializeComponent();
Cts = new CancellationTokenSource();
}
private void Cancel_Click(object sender, EventArgs e)
{
Cts.Cancel();
}
}
}
I've been looking around for quite some time now, but without any solution..
What I want to achieve, is use an EventWaitHandle class in order to pause one thread.
So, I create two buttons on a form. The first one should send a message, then pause, and the second one should unpause the thread of the first button, which then sends another message. Like that:
using System;
using System.Windows.Forms;
using System.Threading;
namespace Application
{
public partial class Form1 : Form
{
EventWaitHandle wh = new AutoResetEvent(false);
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
MessageBox.Show("Thread blocked!");
wh.WaitOne();
MessageBox.Show("Thread unblocked!");
}
private void button2_Click(object sender, EventArgs e)
{
wh.Set();
}
}
}
But as soon as the thread gets blocked with the wh.WaitOne(), I can't do anything on the entire form, including pushing the second button or at least closing it..
What did I do wrong? Because I can't seem to find any difference between examples I could find, and my code.
You have only 1 thread. The UI thread.
When you block it, you block the entire UI.
You'll have to create a second thread.
Try this:
private void button1_Click(object sender, EventArgs e)
{
new Thread() {
void run() {
MessageBox.Show("Thread blocked!");
wh.WaitOne();
MessageBox.Show("Thread unblocked!");
}
}.start();
}
I am new to C# and am having trouble figuring out how to pass an event from a thread up to the GUI form thread. Any help would be appreciated. All of the examples I find are WAY too complicated. I just want to start with one event from the treat up to the GUI and have the GUI do something (right now, anything).
namespace testEvents
{
public delegate void StuffHappenedDel( MessageEventArgs e);
public partial class Form1 : Form
{
workerThread thread;
int j = 0;
public Form1()
{
InitializeComponent();
thread = new workerThread();
thread.Start();
}
private void button1_Click(object sender, EventArgs e)
{
thread.Stop();
}
private void StuffHappenedDel(Object seder, EventArgs e)
{
j++;
}
}
public class workerThread
{
Thread worker;
private bool _quit = false;
/* I don't think this next line is correct*/
public event StuffHappenedDel StuffHappened;
protected virtual void OnStuffHappened(MessageEventArgs e)
{
if (StuffHappened != null)
StuffHappened( e);
}
public void Start()
{
ThreadStart start = new ThreadStart(Run);
worker = new Thread(start);
worker.Start();
}
private void Run()
{
int i = 0;
while (!_quit)
{
Thread.Sleep(1000);
i++;
OnStuffHappened(new MessageEventArgs(false, "it worked!"));
Console.WriteLine(string.Format("Slept {0} seconds.",i));
}
Console.WriteLine("Thread exiting");
}
}
public class MessageEventArgs : EventArgs
{
public MessageEventArgs(bool Error, string message)
{
IsError = Error;
Message = message;
}
public bool IsError { get; set; }
public string Message { get; set; }
}
}
You need to register Form1 as a listener for the event. First, add a method like the following to Form1:
private void thread_SuffHappened(MessageEventArgs e)
{
MessageBox.Show("Stuff happened!");
}
And in Form1's constructor, register as a listener like so:
public Form1()
{
InitializeComponent();
thread = new workerThread();
thread.StuffHappened += new StuffHappenedDel(thread_StuffHappened);
thread.Start();
}
Do you have to use this custom threading system, or are you able to use BackgroundWorkers? BackgroundWorkers haven an event ProgressChanged which fires on the thread that created the BackgroundWorker.
Alternatively, if you attach a handler to a background event from the UI thread, the work is still done on the background thread.
thread.StuffHappenedDel += new EventHandler<MessageEventArgs>(StuffHappenedDel);
Therefore, you need to marshall the data to the UI thread. One way is by using BeingInvoke.
private void StuffHappenedDel(object sender, MessageEventArgs e)
{
this.myControl.BeginInvoke( new Action(
() =>
{
//UI thread work (likely anything that affects UI. Heavy
//processing can continue on the bg thread outside this code block
}));
}
Also, you can use if (myControl.InvokeRequired) to check if you need to marshall data when changing a particular control.
if (this.InvokeRequired)
{
this.Invoke((Action)(() =>
{
//UI thread stuff
}
));
}
Edit to clarify
Your thread object that you've created needs to attach an event handler to the StuffHappenedDel event. To do this, you use something like this
thread.StuffHappenedDel += new EventHandler<MessageEventArgs>(StuffHappenedDel);
before you call thread.Start(). Now, this handler is called
private void StuffHappenedDel(Object seder, MessageEventArgs e)
{
j++;
}
whenever your event is fired.
If you want to make changes to any UI elements, you need to use the Invoke method described above.
Look into the Background Worker Class. Also, you can always fire an event that is handled by your GUI Class (though not on the GUI Thread) and then call Invoke
How do I use Threading to Change some Cell in Gridview? I have a query from database and it uses a lot of time for its query. So It's very slow and I would like to use Threading to load data faster. Also, when the thread has finished it's job can change data in Grid view?
using System.Threading;
using System.Threading.Tasks;
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
dataGridView1.DataSource = new List<Test>() { new Test { Name = "Original Value" } };
}
// Start the a new Task to avoid blocking the UI Thread
private void button1_Click(object sender, EventArgs e)
{
Task.Factory.StartNew(this.UpdateGridView);
}
// Blocks the UI
private void button2_Click(object sender, EventArgs e)
{
UpdateGridView();
}
private void UpdateGridView()
{
//Simulate long running operation
Thread.Sleep(3000);
Action del = () =>
{
dataGridView1.Rows[0].Cells[0].Value = "Updated value";
};
// If the caller is on a different thread than the one the control was created on
// http://msdn.microsoft.com/en-us/library/system.windows.forms.control.invokerequired%28v=vs.110%29.aspx
if (dataGridView1.InvokeRequired)
{
dataGridView1.Invoke(del);
}
else
{
del();
}
}
}