I'm writing a program that handles DBs and writes any changes into ListView for user to proccess them. After that It is suposed to write all changes back into DB but I can't figure out how to reach ListView.ListViewItemCollection from BGW. I've tried to use Control.Invoke but I'm affraid I'm not yet skilled enough to make it work.
The error I'm getting says I can't access that control from thread that it was not created on
private delegate void BGOUdate(ListView.ListViewItemCollection lvic);
BGOU = new BGOUdate(ApplyChanges);
bgw1.RunWorkerAsync(lvProducts.Items);
private void bgwSearcher_DoWork(object sender, DoWorkEventArgs e)
{
BGOU(e.Argument as ListView.ListViewItemCollection);
}
private void ApplyChanges(ListView.ListViewItemCollection lvic)
{
...
foreach (ListViewItem item in lvic)
{
...
}
...
}
Control.Invoke is the right way to walk. But you need to pass the control to the background worker, not just its ItemCollection:
private void ApplyChanges(ListView lv)
{
lv.Invoke((Action)delegate
{
foreach (ListViewItem item in lv.Items)
{
...
}
});
...
}
This way, all changes to the ListViewItems will be made in the thread that created the controls.
Action is a predefined void delegate that takes no arguments. The keyword delegate marks the following block as an anonymous function and returns a delegate to this, which is then cast to an Action that can be invoked on the control. The call to Invoke causes the passed delegate to be executed on the thread that is associated with the control's window handle, which is almost everytime the creator thread.
Maybe, you should consider DataBinding as an option to keep GUI elements in sync with your data.
Related
I am stuck on an issue where I am using Backgroundworker to show the progress of my work in a progress bar. Code used for backgroundworker:-
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
Thread.Sleep(200);
for (int i = 0; i <= 100; i++)
{
Delegate del= new DELEGATE(simulateHeavyWork);
this.Invoke(del);
backgroundWorker1.ReportProgress(i);
if (backgroundWorker1.CancellationPending)
{
e.Cancel = true;
backgroundWorker1.ReportProgress(0);
return;
}
}
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
percentLabel.Text = e.ProgressPercentage.ToString() + "%";
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
MessageBox.Show("Cancelled");
}
else
{
MessageBox.Show("Completed");
}
}
I have created a delegate on the code:-
public partial class Form1 : Form
{
private delegate void DELEGATE();
public Form1()
{
InitializeComponent();
}
private void simulateHeavyWork()
{
Thread.Sleep(100);
...lines of code to perform some search logs operation..
....
}
}
The functionality I want to achieve is that progress bar should report the progress of my function simulateHeavyWork() which is actually using UI thread as it needs to take input from my form controls and update it as well.
Now the problem which is happening is that code is actually calling simulateHeavyWork() and gives the output that is updating ui controls and work is done. (Note: I have used delegate here to avoid error cross controls running on ui thread as my function needs to use UI controls.)
Once that is done, it starts updating progress bar and which is wrong and looks like it calls simulateHeavyWork again and again with the gap of sleep(100).
user3222101, as Andy stated before, you are running simulateHeavyWork() continuously. Moreover, by calling Invoke you are running this method in the UI thread which cause an extra sleep in the UI thread. Basically Invoke uses the message loop (pump) of the Control you use it on (Form1 in that case) and put your delegate to the queue of the UI thread in order to execute. This is not a good practice I think, due to the Sleep() call and time consuming log operations in your simulateHeavyWork() method.
I hope, understand you problem clearly.What I suggest is separation of the time consuming log operations from UI thread. Do not spend the valuable time of UI thread with slow and boring I/O operations. Get the values from the controls (using Invoke in the BackgroundWorker as I will explain below), do whatever you want in BackgroundWorker and update your GUI (again using Invoke) without touching the UI thread for this kind of heavy tasks.
As Andy suggested, you can pass data via the parameter of RunWorkerAsync and you should create a class which can store any data you need (because it accepts only one parameter). However, you can get the values from your Form whenever you need from another thread by using Invoke. Invoke
method also returns the value from your delegate (please see the example at the link below) and this gives you a chance to get the values of your controls on the form. Create a delegate which returns an object of type class that you crated for RunWorkerAsync and use this values in the BackgroundWorker thread. Please, have a look at the example in here.
public static string GetTextThreadSafe(this TextBox box)
{
return GetTextBoxText(box);
}
Also, example uses Func<...> in order to return value.
By this way you can sleep (in BackgroundWorker thread) for a while then get the values from your controls (current values) and do whatever you want (again in BackgroundWorker thread). I think, this improves your code.
From your question: "which is wrong and looks like it calls simulateHeavyWork again and again with the gap of sleep(100)."
Of course it calls. Just look at your code:
for (int i = 0; i <= 100; i++)
{
Delegate del= new DELEGATE(simulateHeavyWork);
this.Invoke(del);
So you are calling simulateHeavyWork 100 times here. And since you've typed Thread.Sleep(100); in the body of simulateHeavyWork - gap between calls is about Sleep(100)
Edit for better understanding...
I have a Form1.cs file and a separate Class.cs file.
In Form1 I have a bgWorker that calls Class.myFunc(), this does three foreach loops, each loop return some values such as string ClientName, or string ClientOrder.
I want to return these values from Class.myFunc to bgWorker (which is inside Form1, remember) and insert them into a ListView, Textbox, or whatever.
So the problem is: How I return string values from Class.myFunc to BgWorker?
I hope someone can help me with this...
Form1.cs
void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
ControlsHelper.ControlInvike(listProcess, () => listProcess.Items.Add("Current").Name = "item1");
myOtherClass cp = new myOtherClass();
cp.myFunc();
}
Class.cs
public void myFunc()
{
foreach (string Client in Clients)
{
// Do something
// Return Client and insert into listview, richtextbox, W/E
}
}
To expand on Daniel Hilgarth's answer: if the BGW doesn't exist in the context of Class.cs can you not pass it in as a parameter so that you can update your progress. In your DoWork signature the object sender is the BGW you want to cast as BackgroundWorker to send into myFunc.
So you'd have
public void myFunc(BackgroundWorker bgw)
{
foreach (string Client in Clients)
{
// Do something
// Return Client and insert into listview, richtextbox, W/E
var returningObjects = List<string>(); //I assume this will be a list of strings based on your question.
returningObjects.Add(ClientName);
returningObjects.Add(ClientOrder);
returningObjects.Add(Client3rdThing);
bgw.ReportProgress(0,returningObjects)
}
}
An even better than using a generic List<string> you could make your own class which has all the items you need then you can call them explicitly instead of referring to a list index.
void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker bgw = sender as BackgroundWorker;
ControlsHelper.ControlInvike(listProcess, () => listProcess.Items.Add("Current").Name = "item1");
myOtherClass cp = new myOtherClass();
cp.myFunc(bgw);
}
Then in your ProgressChanged event handler
private void bgWorker_ProgressChanged(object sender,
ProgressChangedEventArgs e)
{
//Set the e.UserState to whatever you need. It is of type Object.
var returnedObjects = e.Userstate as List<string>;
if(returnedObjects != null)
{
//do stuff with each of your returnedObjects[i];
}
}
To ensure thread safety I use this extension method for controls. It's really great having this logic wrapped up in an extension method. You don't need to think about the Invoke structure whenever you need to call it, you just call it.
And be sure to tie it all together when you instantiate your BackgroundWorker using
bgWorker.ProgressChanged += new ProgressChangedEventHandler(bgWorker_ProgressChanged);
You could (ab)use BackgroundWorker.ReportProgress for this and pass your values as the second parameter.
First, make sure you really need multi threading. You may well be able to ge by with just preventing the GUI from updating until the list load is complete.
If you do need multi threading, make sure your three loops are required to run in that order. If they aren't, then you want 3 bgWorkers instead of 1.
Finally, there are a lot of good articles on thread safety. You will find that you cannot directly update the listviews, you will have to use thread safe delegates.
I'm implementing producer/consumer problem. the code looks like this:
void producer()
{
// produce item
// update some control in form
}
void consumer()
{
// consume item
// update some control in form
}
producer and consumer methods are executed in different threads from the one that created my form, so I can't update controls in form. I tried following code:
void producer()
{
// produce item
// put the work to be done in a queue
this.Invalidate();
}
void consumer()
{
// consume item
// put the work to be done in a queue
this.Invalidate();
}
So now I have to detect if the form has been invalidated. I looked in Form's event list, and the best thing I could find was paint event. I put the code that got the job done, and it works fine. The problem is I somehow doubt I've done this the right way although it works. I think paint is not the right place to do the job, as what I'm doing it not just painting. I was wondering if there's a better way to do this.
Edit -- Snippet for Invalidated event handler not working
public Form1()
{
InitializeComponent();
this.Invalidated += InvalidateEventHandler;
}
void producer(object o)
{
// produce
// put work in queue
this.Invalidate();
}
public void InvalidateEventHandler(object sender, InvalidateEventArgs e)
{
// Do Stuff to form -- Where exception raises
}
Invalidate is intended to trigger a Paint.
What you need is to Control.Invoke() your own refresh method on he form.
Edit:
Your non-GUI threads should not even call Invalidate(), they can't touch the GUI.
You can write your own ProcessData() form-method and from the Prod/Cons call mainForm.Invoke(ProcessData)
Then ProcessData() is responsible for thread-safe access to the data and for Invalidating the GUI
You can try to use new keyword to make your own implementation of Invalidate
public new void Invalidate()
{
// place your logic here
base.Invalidate();
}
Aslo Form has Invalidated event wich is triggered after Ivalidate ends
EDIT:
public void InvalidateEventHandler(object sender, InvalidateEventArgs e)
{
anotherForm.Invoke(new Action(() =>
{
// Do Stuff to form -- Where exception raises
}));
}
I hava a Background Worker and a DataGrid in my c# Application. In do work of my Backgroundworker which will call an Api in my dlls which will enter some Data in a SQLite Database. After the Completion of my Api call I report a progress and In progress event of my Backgroundworker I get the contents from Db and assign it as a DataSource to my grid. I call same API in same backgroundworker. In the middle of processing my application crashes. But If I dont assign the dataSource in ProgressChanged my application doesnt crashes.
I am assuming you must be accessing UI object using Invoke method.
If not try using following approach (Executes the specified delegate, on the thread that owns the control's underlying window handle, with the specified list of arguments.):
//In Form.Designer.cs
Label myLabel = new Label();
//In code behind under Background worker method
LabelVlaueSetter SetLabelTextDel = SetLabelText;
if (myLabel .InvokeRequired)
{
myLabel.Invoke(SetLabelTextDel, "Some Value");
}
private delegate void LabelVlaueSetter(string value);
//Set method invoked by background thread
private void SetLabelText(string value)
{
myLabel.Text = value;
}
As Johnathan Allen mentions, it should not matter. Unless something else is going on. I have had two cases where I could not interact with certain controls in the events generated by the BackgroundWorker. The only thing that worked was using the Invoke method.
Try assigning the DataSource on the same thread that created the DataGridView control. You do this through the control's Invoke method. Use the following code. (I have not tested, but this is the standard pattern.)
If this does not work then try Jonathan Allen's suggestion.
Actually, do whichever suggestion is easiest to try first.
private delegate void SetDataSourceDelegate(object value);
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) {
DataTable oData = null; //'assign data source
if (dataGridView1.InvokeRequired) {
dataGridView1.Invoke(new SetDataSourceDelegate(SetDataSource), new Object[] {oData});
}else{
SetDataSource(oData);
}
}
private void SetDataSource(object value) {
dataGridView1.DataSource = value;
}
It shouldn't matter, but why are you using ProgressChanged instead of RunWorkerCompleted?
Also, try doing everything on the GUI thread without the BackgroundWorker. That will let you know if the problem is in your code or how your code interacts with the GUI.
It's not the first time I come across delegates and I am as confused as I were the last time and the time before that. So once and for all I want to get the delgate-confusion cleared up.
My problem is as followed
Having a Graphical User Interface which only displays a ListView with some boud items, I want to load the data from a Data Connection which takes some time, to increase the comfort of using the application I have instancieted a BackgroundWorker and in the doWork-method I want to fetch the data and display it.
This is how I want it
Create BackgroundWorker and appoint doWork_fetchData() method to the doWork event
Call the Async method of my Worker instance
Have the ListView updated without the User Interface beeing "frozen" during download of data.
Now this is Cross-Thread-Invoking and I wanted to solve this with Delegates which brings me here. Following this tutorial, I got a working Delegate, However it did not solve the problem, inside my delegate I cannot change my ListView, it still says it is on another thread.
I want to find an Easy explenation on delegates and how to use them to solve my problem. Also, should I think or design my software different?
Normally BackgroundWorker communicates with the UI thread using ReportProgress. You would hook up a delegate to receive those progress events before launching the background worker, and then the progress would be reported on the UI thread, where you're safe to change your ListView.
The other alternative in Windows Forms is to call Control.Invoke or Control.BeginInvoke, passing in a delegate which will update the UI. That delegate will be executed on the UI thread. For an example of this, see my threading tutorial or Joe Albahari's.
The equivalent of this in WPF is the Dispatcher - again, Invoke and BeginInvoke. You can access the dispatcher for a control with the Dispatcher property.
You can't change a ui control from a different thread directly, you need to check the Control.InvokeRequired property before you make a change.
See this example on msdn
Checkout this code, it does what you need:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void btnFill_Click(object sender, EventArgs e)
{
backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
backgroundWorker1.RunWorkerAsync();
}
private delegate void AddItemToListViewDelegate(ListView view, ListViewItem item);
private void AddItemToListView(ListView view, ListViewItem item)
{
if (InvokeRequired)
{
Invoke(new AddItemToListViewDelegate(AddItemToListView), new object[] { view, item });
return;
}
view.Items.Add(item);
}
private delegate void ClearListViewItemsDelegate(ListView view);
private void ClearListView(ListView view)
{
if (InvokeRequired)
{
Invoke(new ClearListViewItemsDelegate(ClearListView), new object[] { view });
return;
}
view.Items.Clear();
}
void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i < 100; i++)
{
if (i == 0)
ClearListView(listView1);
var item = new ListViewItem();
item.Name = i.ToString();
item.Text = item.Name;
AddItemToListView(listView1, item);
}
}
}
And for WPF something similar is required. Note this is not working code. As I don't use WPF I can't vouch that this is solid code, but it should give you an idea. You may need to create an type derived from EventArgs to encapsulate your listview and listviewitems.
If I get time, I'll edit this post so that it works, but that will have to wait until this evening!
using System.Windows.Threading;
...
if (listView1.Dispatcher.Thread != Thread.CurrentThread)
{
listView1.Dispatcher.BeginInvoke(
DispatcherPriority.Normal,
new EventHandler<ListViewAddEventArgs>(AddItemToListView), sender, new object[] { e } );
return;
}
listView1.Items.Add(e.File);