Manipulate ListView from inside a background worker - c#

I'm using a background worker to loop through each item in a ListView and do work on it after a button is clicked:
private void bParsePosts_Click(object sender, EventArgs e)
{
parseWorker.RunWorkerAsync(this.lvPostQueue);
}
Then, I have:
private void parseWorker_DoWork(object sender, DoWorkEventArgs e)
{
// Loop through each item
for (int i = 0; i < lvPostQueue.Items.Count; i++)
{
string title = lvPostQueue.Items[i].SubItems[0].ToString();
string category = lvPostQueue.Items[i].SubItems[1].ToString();
string url = lvPostQueue.Items[i].SubItems[2].ToString();
lvPostQueue.Items[i].SubItems[3].Text = "Done";
}
}
However, I get this error:
Cross-thread operation not valid: Control 'lvPostQueue' accessed from a thread other than the thread it was created on.
How would I go about manipulating the lvPostQueue control from within that background worker?
Thanks.

The right answer would be:
private void bParsePosts_Click(object sender, EventArgs e)
{
parseWorker.WorkerReportsProgress = true;
parseWorker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);
parseWorker.RunWorkerAsync();
}
private void parseWorker_DoWork(object sender, DoWorkEventArgs e)
{
// Loop through each item
for (int i = 0; i < lvPostQueue.Items.Count; i++)
{
string title = lvPostQueue.Items[i].SubItems[0].ToString();
string category = lvPostQueue.Items[i].SubItems[1].ToString();
string url = lvPostQueue.Items[i].SubItems[2].ToString();
parseWorker.ReportProgress(i * 100 / lvPostQueue.Items.Count, i);
}
}
void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
var i = (int)e.UserState;
lvPostQueue.Items[i].SubItems[3].Text = "Done";
}

Simply use thread safe calls: http://msdn.microsoft.com/en-us/library/ms171728.aspx
Example:
// This event handler starts the form's
// BackgroundWorker by calling RunWorkerAsync.
//
// The Text property of the TextBox control is set
// when the BackgroundWorker raises the RunWorkerCompleted
// event.
private void setTextBackgroundWorkerBtn_Click(
object sender,
EventArgs e)
{
this.backgroundWorker1.RunWorkerAsync();
}
// This event handler sets the Text property of the TextBox
// control. It is called on the thread that created the
// TextBox control, so the call is thread-safe.
//
// BackgroundWorker is the preferred way to perform asynchronous
// operations.
private void backgroundWorker1_RunWorkerCompleted(
object sender,
RunWorkerCompletedEventArgs e)
{
this.textBox1.Text =
"This text was set safely by BackgroundWorker.";
}

Related

How can I make it work this "responsive Progressbar"?

Good morning, I'm trying to write an application that use in his interface a progressbar (in C#, WPF). I have read about the need of perform the UI task in a different thread, using Backgroundworker. I trying to make it work using a lot of information, but nothing happens (the program work fine, but the progressbar only shown at the end of the "hard-work tasks").
I'm civil engineer (not a software one), so I ask if anyone can help me with that.
namespace SAP2000___Quake_Definitions
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private readonly BackgroundWorker bgWoker = new BackgroundWorker();
public MainWindow()
{
InitializeComponent();
this.bgWoker.WorkerReportsProgress = true;
this.bgWoker.WorkerSupportsCancellation = true;
this.bgWoker.DoWork += bgWorker_DoWork;
this.bgWoker.ProgressChanged += bgWorker_ProgressChanged;
this.bgWoker.RunWorkerCompleted += bgWorker_RunWorkerCompleted;
}
private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.progBar.Value = e.ProgressPercentage;
}
private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker bgWorker = (BackgroundWorker)sender;
Dispatcher.Invoke(new Action(() => DoTheHardWork()));
}
private void processButton_Click(object sender, RoutedEventArgs e)
{
this.bgWoker.RunWorkerAsync();
}
private void DoTheHardWork()
{
switch (this.chckBox2.IsChecked.GetValueOrDefault())
{
case true:
this.bgWoker.ReportProgress(0);
//more hardwork with inputs from WPF
case false:
this.bgWoker.ReportProgress(0);
//more hardwork with inputs from WPF
}
}
}
}
That is not how you should be using a BackgroundWorker. I wrote some example code a few years back. It should get you on the right track:
#region Primenumbers
private void btnPrimStart_Click(object sender, EventArgs e)
{
if (!bgwPrim.IsBusy)
{
//Prepare ProgressBar and Textbox
int temp = (int)nudPrim.Value;
pgbPrim.Maximum = temp;
tbPrim.Text = "";
//Start processing
bgwPrim.RunWorkerAsync(temp);
}
}
private void btnPrimCancel_Click(object sender, EventArgs e)
{
if (bgwPrim.IsBusy)
{
bgwPrim.CancelAsync();
}
}
private void bgwPrim_DoWork(object sender, DoWorkEventArgs e)
{
int highestToCheck = (int)e.Argument;
//Get a reference to the BackgroundWorker running this code
//for Progress Updates and Cancelation checking
BackgroundWorker thisWorker = (BackgroundWorker)sender;
//Create the list that stores the results and is returned by DoWork
List<int> Primes = new List<int>();
//Check all uneven numbers between 1 and whatever the user choose as upper limit
for(int PrimeCandidate=1; PrimeCandidate < highestToCheck; PrimeCandidate+=2)
{
//Report progress
thisWorker.ReportProgress(PrimeCandidate);
bool isNoPrime = false;
//Check if the Cancelation was requested during the last loop
if (thisWorker.CancellationPending)
{
//Tell the Backgroundworker you are canceling and exit the for-loop
e.Cancel = true;
break;
}
//Determin if this is a Prime Number
for (int j = 3; j < PrimeCandidate && !isNoPrime; j += 2)
{
if (PrimeCandidate % j == 0)
isNoPrime = true;
}
if (!isNoPrime)
Primes.Add(PrimeCandidate);
}
//Tell the progress bar you are finished
thisWorker.ReportProgress(highestToCheck);
//Save Return Value
e.Result = Primes.ToArray();
}
private void bgwPrim_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
pgbPrim.Value = e.ProgressPercentage;
}
private void bgwPrim_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
pgbPrim.Value = pgbPrim.Maximum;
this.Refresh();
if (!e.Cancelled && e.Error == null)
{
//Show the Result
int[] Primes = (int[])e.Result;
StringBuilder sbOutput = new StringBuilder();
foreach (int Prim in Primes)
{
sbOutput.Append(Prim.ToString() + Environment.NewLine);
}
tbPrim.Text = sbOutput.ToString();
}
else
{
tbPrim.Text = "Operation canceled by user or Exception";
}
}
#endregion
You have to limit all UI writing work to the Progress Report and Run wokrer compelte Events. Those will be raised in the thread that created the BGW (wich should be the UI thread) automagically.
Note that you can only report progress between distinct steps. I had the advantage that I had to write the loop anyway. But if you have existing code (like most download or disk code), you can usually only report between files.
my mistakes were three:
Trying to use "Dispatcher.Invoke(new Action(() => DoTheHardWork()));" to solve an exception related to my thread (exception caused by point #3).
Avoiding the instantiation: BackgroundWorker bgWorker = (BackgroundWorker)sender (thank you #Christopher).
Writing a code that manipulate a UI-Component inside the DoWork event handle of my Backgroundworker. MSDN says: You must be careful not to manipulate any user-interface objects in your DoWork event handler. Instead, communicate to the user interface through the ProgressChanged and RunWorkerCompleted events. Trying this, the exception occur.
Solving the point #2 and #3, the UI is perfectly responsive respect to the "hardwork" function (runned in background).

Problems of while(true) in C# "VS2012" {WinForm}

" int ans = 2;
private void Form1_Load(object sender, EventArgs e)
{
for (int i = 0; i <21; i++)
{
ans = 2;
label1.Text += i.ToString();
while (true)
{
if (ans == 1)
{
break;
}
}
}
}
private void button1_Click(object sender, EventArgs e)
{
ans = 1;
} "
this is a simple app
I want to print a number & then wait to the button to be clicked to break the while loop
but when I run the application , the form doesn't show .
"T think that the problem is the while (true)".
what to do?
Use a timer. Start the timer when the form loads. Each time it ticks, increment the number and display it. On button click, you just need to stop the timer.
private Timer _myTimer;
private int number = 0;
private void Form1_Load(object sender, EventArgs e)
{
_myTimer = new Timer();
_myTimer.Interval = 1; // 1 millisecond
_myTimer.Tick += new EventHandler(MyTimer_Tick);
_myTimer.Start();
}
// increments the number at timer tick
private void MyTimer_Tick(object sender, EventArgs e)
{
number ++;
// TODO: update UI here
}
// Stops the timer
private void button1_Click(object sender, EventArgs e)
{
_myTimer.Stop();
}
It's best to not use a loop here. Since this loop won't end you won't ever leave Form_Load and it won't display the form. If you were trying to do some task when the user clicks a button, why not move that logic to button1_Click?
The correct way to implement such a task as you describe would be as such:
private EventWaitHandle ewh = new EventWaitHandle(false, EventResetMode.AutoReset);
private void Form1_Load(object sender, EventArgs e)
{
Task.Factory.StartNew(() =>
{
for (int i = 0; i < 26; i++)
{
ewh.WaitOne();
Action updateLable = () => label1.Text = "" + i;
label1.BeginInvoke(updateLable);
}
});
}
private void button1_Click(object sender, EventArgs e)
{
ewh.Set();
}
As you can see I've replaced your busy wait (while(true)) with a .Net wait handle.
One of the answers describes a timer that acts every millisecond - that is a busy wait of sorts.
This is what async/await is for. Mark your Load() event with "async", then "await" a Task that continues when a ManualResetEvent is triggered in the Button click handler:
private System.Threading.ManualResetEvent mre = new System.Threading.ManualResetEvent(false);
private async void Form1_Load(object sender, EventArgs e)
{
for (int i = 0; i < 21; i++)
{
label1.Text = i.ToString();
mre.Reset();
await Task.Factory.StartNew(() => { mre.WaitOne(); });
}
button1.Enabled = false;
label1.Text = "Done!";
}
private void button1_Click(object sender, EventArgs e)
{
mre.Set();
}

How to update a listbox dynamically?

I am making a search utility and using a BackgroundWorker to search. I want that as soon as the first result is found , a new window should open up with a ListBox with the first element displayed. Now, I want that as soon as subsequent results are found, the ListBox should be updated with those results.
The method thought by me was to report the progress as soon as results are found and pass "New" and "Update" as userState to the method.
Based on the userState, I can decide whether to create a new Form or update and existing one.
Here is the code :-
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
if (e.UserState.ToString() == "New")
{
Form ResultForm = new Form();
ResultForm.Name = "ResultForm" + i.ToString();
LastFormName = ResultForm.Name;
ListBox ResultListBox = new ListBox();
ResultListBox.DataSource = SearchList;
ResultListBox.Name = "ResultListBox" + i.ToString();
LastListName = ResultListBox.Name ;
ResultForm.Container.Add(ResultListBox);
ResultListBox.Show();
ResultForm.Show();
i++;
}
else
{
;
}
}
I have stored the names of the Last open Form and it's ListBox in the variables LastFormName and 'LastListName'.
I am unable to understand what to put in the else condition, so as to update the ListBox.
What I would do is expose some properties on the popup form so that you can tell if it is open and have access to the list box.
public partial class Popup : Form
{
public bool isOpen;
public ListBox PopupListBox;
public Popup()
{
InitializeComponent();
}
void Popup_FormClosing(object sender, FormClosingEventArgs e)
{
isOpen = false;
}
private void Popup_Load(object sender, EventArgs e)
{
this.FormClosing += Popup_FormClosing;
PopupListBox = popupListBox;
}
}
Then on the calling form I would subscribe to the ProcessedChanged Event and update the listbox with the data you are passing through the ProcessedChangedEventArgs. Here is the code for the calling form
public partial class Form1 : Form
{
Popup popupForm = new Popup();
BackgroundWorker backgroundWorker = new BackgroundWorker();
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
backgroundWorker.WorkerSupportsCancellation = true;
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.DoWork += backgroundWorkerDoWork;
backgroundWorker.ProgressChanged += backgroundWorkerProgressChanged;
backgroundWorker.RunWorkerCompleted += backgroundWorkerRunWorkerCompleted;
}
void backgroundWorkerRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if ((e.Cancelled == true))
{
//What do you want to do if Cancelled?
}
else if (!(e.Error == null))
{
//What do you want to do if there is an error?
}
else
{
//What do you want to do when it is done?
}
}
void backgroundWorkerProgressChanged(object sender, ProgressChangedEventArgs e)
{
if (!popupForm.isOpen || popupForm == null)
{
popupForm = new Popup();
popupForm.Show();
popupForm.isOpen = true;
}
else
{
popupForm.Activate();
popupForm.WindowState = FormWindowState.Normal;
}
popupForm.PopupListBox.Items.Add(e.ProgressPercentage.ToString() + "%");
}
void backgroundWorkerDoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
for (int i = 1; (i <= 10); i++)
{
if ((worker.CancellationPending == true))
{
e.Cancel = true;
break;
}
else
{
// Perform a time consuming operation and report progress.
System.Threading.Thread.Sleep(500);
worker.ReportProgress((i * 10));
}
}
}
private void buttonStart_Click(object sender, EventArgs e)
{
if (backgroundWorker.IsBusy != true)
{
backgroundWorker.RunWorkerAsync();
}
}
private void buttonCancel_Click(object sender, EventArgs e)
{
if (backgroundWorker.WorkerSupportsCancellation == true)
{
backgroundWorker.CancelAsync();
}
}
}
You shouldn't be doing work in the ProgressChanged event handler
You won't have access to the results as it is only passed an int for progress and user state
This is on the UI thread. The entire point is to do your processing on a background thread
The name DoWork event handler is clear that this is where you should do your processing.
In answer to your question. Since you create the ListBox in your event handler it goes out of scope outside the if statement. You need to create this in a more global scope. Then to add to it ResultListBox.Items.Add("ResultListBox" + i.ToString());

Running a method in BackGroundWorker and Showing ProgressBar

What I want is when some method is doing some task UI keeps itself active and I want to show the progress of the work in a progress-bar.
I have a method, a BackGroundWorker and a Progressbar. I want to call the method when BackGroundWorker starts running and show the progress. The method contains a loop. So, it can report the progress.
So, what can be done?
private void Form1_Load(object sender, EventArgs e)
{
// TODO: This line of code loads data into the 'dataSet1.TBLMARKET' table. You can move, or remove it, as needed.
myBGWorker.WorkerReportsProgress = true;
}
private void myBGWorker_DoWork(object sender, DoWorkEventArgs e)
{
parseFiles();
}
private void myBGWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
myProgressBar.Value = e.ProgressPercentage;
}
private void myBGWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show("Done");
}
private void parseButton_Click(object sender, EventArgs e)
{
myBGWorker.RunWorkerAsync();
}
public void parseFiles()
{
for()
{
//parsing
myBGWorker.ReportProgress(...);
}
}
But it's not working. The Progressbar is not updating. Only a small progress is showing after the MessageBox "Done".
Instead of using one ParseFiles method (which should depend on myBGWorker) use loop and method which parse one file. Report progress percentage in that loop:
private void parseButton_Click(object sender, EventArgs e)
{
parseButton.Enabled = false;
myBGWorker.RunWorkerAsync();
}
private void myBGWorker_DoWork(object sender, DoWorkEventArgs e)
{
for(int i = 0; i < filesCount; i++)
{
ParseSingleFile(); // pass filename here
int percentage = (i + 1) * 100 / filesCount;
myBGWorker.ReportProgress(percentage);
}
}
void myBGWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
myProgressBar.Value = e.ProgressPercentage;
}
void myBGWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
parseButton.Enabled = true;
MessageBox.Show("Done");
}
To. soham.m17
using with sender argument
private void myBGWorker_DoWork(object sender, DoWorkEventArgs e)
{
var worker = sender as BackgroundWorker;
for(int i = 0; i < filesCount; i++)
{
ParseSingleFile(); // pass filename here
int percentage = (i + 1) * 100 / filesCount;
worker.ReportProgress(percentage); // use not myBGWorker but worker from sender
}
}
I am sorry about the question. Actually the code works fine. It was not showing the Progressbar as the argument in myBGWorker.ReportProgress() was fraction and not percentage. So, it was not showing it. Sorry for the inconvenience.
Moderator may delete this thread. Otherwise it can be a tutorial for others.

BackgroundWorker progressBar - Problem implementing ReportProgress when loop occurs in another class

Hi
I am trying to integrate a background worker with a progress bar but cannot get it right.
I am processing some files and all the processing is done in an external class.
My difficulty is that the looping is inside this class,where usually I do the talking to the backgroundworker.
The good thing is that when processing a file,an event is fired whenever each file has completed processing.
This is my code any suggestions how could this be achieved
BackgroundWorker _bw = new BackgroundWorker
private void RunLongProcess()
{
_bw.WorkerReportsProgress = true;
_bw.WorkerSupportsCancellation = true;
_bw.DoWork += DoWork;
_bw.ProgressChanged += ProgressChanged;
_bw.RunWorkerCompleted += RunWorkerCompleted;
_bw.RunWorkerAsync();//start the process
if (_bw.IsBusy)
_bw.CancelAsync();
}
static void DoWork (object sender, DoWorkEventArgs e)
{
var files=GetFiles();
int fileCount=files.Count;
//usually I do a loop here but all the processing is done inside this class so
var fileProcessor=new FileProcesser();
fileProcessor.ProcessFiles(files);
}
private void OnFileProcessCompleted(object sender, FileEventArgs e)
{
//Event Fired when a file has been processed
//How do I update progressBar.Problem cross threading here.
//What do I do here?????
_bw.ReportProgress(e.FileProcessedCount, e);
}
ProgressChanged (object sender, ProgressChangedEventArgs e)
{
// Update the UI
labelProgress.Text = e.UserState;
progressBar.Value = e.ProgressPercentage;
}
private void RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
// Console.WriteLine("You canceled!");
else if (e.Error != null)
//Console.WriteLine("Worker exception: " + e.Error.ToString());
else
// Console.WriteLine("Complete: " + e.Result);
}
_bw.ReportProgress(e.FileProcessedCount, e); should not go in OnFileProcessCompleted. This event is fired when DoWork completes. You should place that in DoWork to update the progress bar. So it would look something like this:
private void DoWork (object sender, DoWorkEventArgs e)
{
BackgroundWorker bg = sender as BackgroundWorker;
var files = GetFiles();
int fileCount = files.Count;
var fileProcessor = new FileProcesser();
for(int i = 0; i < fileCount; i++)
{
fileProcessor.ProcessFile(files[i]);
bg.ReportProgress( (uint)((i / (double)fileCount) * 100));
}
}
I would pass in the BackgroundWorker to the FileProcessor like this:
private void DoWork (object sender, DoWorkEventArgs e)
{
BackgroundWorker bg = sender as BackgroundWorker;
var files = GetFiles();
int fileCount = files.Count;
var fileProcessor=new FileProcesser(bg);
fileProcessor.ProcessFiles(files);
}
In the FileProcesser, it would look something like this:
private BackgroundWorker _bg;
public FileProcessor(BackgroundWorker bg)
{
_bg = bg;
}
public void ProcessFiles(Files files)
{
// Process files
// ...
// Report Progress
_bg.ReportProgress(e.FileProcessedCount, e);
}
In that case, one solution is to make a derived class from BackgroundWorker, make it subscribe to the event, and have it send the progress event to the UI thread in the event handler.

Categories