In my XML editor I want to be able to open multiple files at once using an index file. Obviously, depending on the amount of files, this can take a bit of time and I want to use a progress bar to notify the user that the programm is still loading and doing something.
From what I have researched the way to keep the UI progress bar updated is using a BackgroundWorker.
public MainWindow()
{
InitializeComponent();
tabList = new ObservableCollection<FileTab>();
tabControl.ItemsSource = tabList;
backgroundWorker = new BackgroundWorker();
backgroundWorker.DoWork += backgroundWorker_DoWork;
backgroundWorker.ProgressChanged += backgroundWorker_ProgressChanged;
backgroundWorker.RunWorkerCompleted += backgroundWorker_RunWorkerCompleted;
backgroundWorker.WorkerReportsProgress = true;
}
(...)
private void OpenProjectButtonClick(object sender, RoutedEventArgs e)
{
openingProgressBar.Value = 0;
openingProgressBar.Visibility = System.Windows.Visibility.Visible;
backgroundWorker.RunWorkerAsync();
}
void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
int i = 0;
foreach (IndexFile file in indexManager.fileList)
{
this.Dispatcher.Invoke((Action)(() =>
{
tabList.Add(new FileTab(file.filePath));
}));
i++;
Console.WriteLine("-(DoWork)->" + i);
double percentage = (Convert.ToDouble(i) / Convert.ToDouble(indexManager.fileList.Count)) * 100;
Console.WriteLine("-(DoWork.percentage)-> "+ percentage);
backgroundWorker.ReportProgress((int)percentage);
}
}
void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
openingProgressBar.Value = e.ProgressPercentage;
Console.WriteLine("-(ProgressChanged)->" + openingProgressBar.Value);
}
void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
openingProgressBar.Visibility = System.Windows.Visibility.Collapsed;
backgroundWorker.Dispose();
Console.WriteLine("-(RunWorkerComplete)-> Done");
}
Since I'm accessing the tablist in the DoWork-Method I wrap that call in the Dispathcer.Invoke. In this form the code kind of does what I want. It makes the collapsed progressBar visible and updates it every once in a while. Sadly it doesn't update the percentage after every file loaded. From what I can see in the console, the ProgressChanged execution lags behind the DoWork. From my understanding it is called in every iteration of the loop though. And even if it fires the UI doesn't always respond to that.
So my question is: Am I still blocking the UI thread somehow and how could I fix it?
The Problem is that
you are do a coupling between UI and BackgroundWorker
The Solution
return on ReportProgress your FileTab object
void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
int i = 0;
foreach (IndexFile file in indexManager.fileList)
{
i++;
Console.WriteLine("-(DoWork)->" + i);
double percentage = (Convert.ToDouble(i) / Convert.ToDouble(indexManager.fileList.Count)) * 100;
Console.WriteLine("-(DoWork.percentage)-> "+ percentage);
backgroundWorker.ReportProgress((int)percentage,new FileTab(file.filePath));
}
}
then add your FileTab object in your backgroundWorker_ProgressChanged to the tabList
Try to move the reporting progress operation into the dispatcher's operations:
foreach (IndexFile file in indexManager.fileList)
{
this.Dispatcher.Invoke((Action)(() =>
{
tabList.Add(new FileTab(file.filePath));
Console.WriteLine("-(DoWork)->" + i);
double percentage = (Convert.ToDouble(i) / Convert.ToDouble(indexManager.fileList.Count)) * 100;
Console.WriteLine("-(DoWork.percentage)-> "+ percentage);
backgroundWorker.ReportProgress((int)percentage);
}));
i++;
}
It's possible that dispatcher runs in another thread...
Related
I want to create a basic multi-thread application using a progress bar. Meaning that this progress bar will run on a different thread while the main thread is busy in the large process it is doing. I've seen a lot of tutorials about it. But the thing that they are multi-threading is the one that doing the large process. The progress bar in the other form is just showing a simple progress bar that runs and complete using a timer.
This is the code I have now.
For the thread:
public void thread()
{
Form6 for6 = new Form6();
for6.Show();
}
TH1 = new Thread(thread);
TH1.Start();
For the progress bar (Code inside form 6)
private void timer1_Tick(object sender, EventArgs e)
{
progressBar1.Increment(+1);
if (progressBar1.Value == 99)
{
this.Close();
}
}
private void Form6_Load(object sender, EventArgs e)
{
timer1.Start();
}
My problem is the thread in here doesn't run the Form6. Is there any way for me to do this?
Instead of using main thread for large processing you can use the Background worker for all the processing.
Here's a simple example to do it.
public partial class Form1 : Form
{
BackgroundWorker bgw = new BackgroundWorker();
public Form1()
{
InitializeComponent();
label1.Text = "";
label2.Text = "";
}
private void button1_Click_1(object sender, EventArgs e)
{
bgw.DoWork += new DoWorkEventHandler(bgw_DoWork);
bgw.ProgressChanged += new ProgressChangedEventHandler(bgw_ProgressChanged);
bgw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgw_RunWorkerCompleted);
bgw.WorkerReportsProgress = true;
bgw.RunWorkerAsync();
}
void bgw_DoWork(object sender, DoWorkEventArgs e)
{
int total = 57; //some number (this is your variable to change)!!
for (int i = 0; i <= total; i++) //some number (total)
{
System.Threading.Thread.Sleep(100);
int percents = (i * 100) / total;
bgw.ReportProgress(percents, i);
//2 arguments:
//1. procenteges (from 0 t0 100) - i do a calcumation
//2. some current value!
}
}
void bgw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
label1.Text = String.Format("Progress: {0} %", e.ProgressPercentage);
label2.Text = String.Format("Total items transfered: {0}", e.UserState);
}
void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//do the code when bgv completes its work
}
}
Instead of the ProgressBar, you should really move your long-running, non-UI code into a separate thread. The standard and easier way of doing this in WinForms is to use BackgroundWorker component, which can raise ProgressChanged event where you can update your UI. Important to note that ProgressChanged event is raised on the UI thread, not on the worker thread, so you don't even need to use Invoke() to perform UI operations (such as updating your ProgressBar).
you must use Control.Invoke for avoid cross-threading problem,but I prefer use BackgroundWorker for resolve it, create Form6 on a _field and use progressbar in ProgressChanged event for more information see this page
public void thread()
{
Form6 for6=null;
Application.OpenForms[0].Control.Invoke(delegate{
for6 = new Form6();
for6.Show();
});
}
Hi I am trying to show a progress bar while in background the code is looping till it finds a file with specific name.
I have written following code for that but the progress bar values doesn't change.
What should I change in the code below?
public partial class Form1 : Form
{
BackgroundWorker backgroundWorker1 = new BackgroundWorker();
public Form1()
{
InitializeComponent();
progressBar1.Visible = false;
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.DoWork +=
new DoWorkEventHandler(backgroundWorker1_DoWork);
backgroundWorker1.ProgressChanged +=
new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);
backgroundWorker1.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(backgroundWorker1_WorkDone);
}
private void button1_Click(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
progressBar1.Visible = true;
}
void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
// Your background task goes here
for (int i = 0; i <= 100; i++)
{
// Report progress to 'UI' thread
backgroundWorker1.ReportProgress(i);
// Simulate long task
while (!File.Exists(#"C:\Users\Test.txt"))
{
continue;
}
}
}
void backgroundWorker1_ProgressChanged(object sender,
ProgressChangedEventArgs e)
{
// The progress percentage is a property of e
progressBar1.Value = e.ProgressPercentage;
}
void backgroundWorker1_WorkDone(object sender,
RunWorkerCompletedEventArgs e)
{
progressBar1.Visible = false;
}
}
The continue in your code will simply continue to the next iteration of the while(true) loop. It won't go back up to the for loop as you expect - it just keeps looping in there over and over again. If it wasn't a background worker, it'd hang your entire program. Since it is, it just hangs that thread. I expect that one CPU core stays at 100% while this is running.
That being said, while the goal here is admirable, there's no good way to accomplish it. Even if you fix the infinite loop, your progress bar will either "finish" at a low percentage (1%, 2%, 3%, done) or go up to 100, then stop updating, but without the file yet existing.
I have the following code to update the progress bar in async fashion and i notice
its async behaviour through the call to MessageBox.In this case it works perfectly
but when i give a sleep of 1s(1000) the MessageBox doesnot pops up and the the complete progress bar fills at once.
Kindly tell why this is happening.
private void button1_Click(object sender, EventArgs e)
{
Update_Async async = new Update_Async(Update_Async_method);
progressBar1.BeginInvoke(async,10);
MessageBox.Show("Updation In Progress");
}
public void Update_Async_method(int a)
{
this.progressBar1.Maximum = a;
for (int i = 1; i <= a; i++)
{
progressBar1.Value = a;
Thread.Sleep(10);
//Thread.Sleep(1000);
}
}
Try Update_Async.BeginInvoke(async, 10) instead if you want the delegate to run asynchrnously but, you'll have to cross thread checking on the update to the progress bar.
In response to your comment, very similar to what you are doing already,
void UpdatingFunction(int value)
{
if (this.progressBar.InvokeRequired)
{
this.progressBar.BeginInvoke(UpdatingFunction, value);
return;
}
// Invoke not required, work on progressbar.
}
This also explains what the Invoke methods on controls are for.
Delegate.BeginInvoke will run a method in a thread once and then dispose it. It is a poor choice if you want to repeatedly do some work in a thread and return intermediate results. If that is what you want, you should use BackgroundWorker. Highly abbreviated snippet:
BackgroundWorker bw;
YourFormConstructor()
{
...
bw = new BackgroundWorker();
bw.WorkerReportsProgress = true;
bw.DoWork += BackgroundCalculations;
bw.ProgressChanged += ShowBackgroundProgress;
}
private void button1_Click(object sender, EventArgs e)
{
bw.RunWorkerAsync(10);
}
void ShowBackgroundProgress(object sender, ProgressChangedEventArgs e)
{
this.progressBar.Value = e.ProgressPercentage;
}
static void BackgroundCalculations(object sender, DoWorkEventArgs e)
{
BackgroundWorker bw = sender as BackgroundWorker;
int max = (int)e.Argument;
for (int i = 0; i < max; i++)
{
bw.ReportProgress(i * 100 / max);
Thread.Sleep(10);
}
bw.ReportProgress(100);
}
}
I am trying to Design a WinForms control in C# which will get some data from a database while it's loading.
I want to use a progress bar to show the progress.
I tried this code (and also many others):
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
this.Show();
progressBar1.Value = 10;
int n;
n = 50;//number of records in DB ,
double progress = 0;
double progstep = 25 / n;
for (int i = 1; i <= n; i++)
{
//getting
if (progress <= 100)
progressBar1.Value = (int)progress;
}
progressBar1.Value = 35;
n = 100;//number of records in DB for another data reading from DB ,
progress = 35;
progstep = 65 / n;
for (int i = 1; i <= n; i++)
{
//getting data from DB
dataGridView1.Rows.Add(....);
//Adding that data to a datagrid -- parametrs removed.
progress += progress;
if (progress <= 100)
progressBar1.Value = (int)progress;
}
}
But, the problem is that the form will wait until data reading progress is completed, and I can see just a full progress bar and all data loaded.
What should I do to fix this?
Since this is winforms, i'd recommend using a BackgroundWorker.
Basic example:
bgWorker.DoWork += new DoWorkEventHandler(bgWorker_DoWork);
bgWorker.ProgressChanged += new DoWorkEventHandler(bgWorker_ProgressChanged);
bgWorker.RunWorkerAsync(//pass in object to process)
Which would then kickoff:
private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
//Do all of your work here
bgWorker.ReportProgress(); //percent done calculation
}
Then the Progress changed event would fire to update the UI safely:
private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar.Value = e.ProgressPercentage;
}
Add a backgroundWorker1 to your form.
Then add a YourForm_Shown event
private void YourForm_Shown(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
}
Add on form's constructor after InitializeComponent()
backgroundWorker1.WorkerReportsProgress = true;
// This event will be raised on the worker thread when the worker starts.
backgroundWorker1.DoWork += new
DoWorkEventHandler(backgroundWorker1_DoWork);
// This event will be raised when we call ReportProgress.
backgroundWorker1.ProgressChanged += new
ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);
And last add the voids of backgroundWorker1:
void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// The progress percentage is a property of e
progressBar1.Value = e.ProgressPercentage;
}
And:
void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 1; i <= n; i++)
{
//getting data from DB.
dataGridView1.Rows.Add(....);
//Adding that data to a datagrid -- parametrs removed.
backgroundWorker1.ReportProgress(i);
// Simulate long task
}
}
This is simple mockup to show you how to work with background worker:
First in your OnLoad create background worker and attach 2 events to it:
var bw = new BackgroundWorker();
bw.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(WorkCompleted);
bw.DoWork += new DoWorkEventHandler(DoWork);
bw.RunWorkerAsync(data); // Assume data is list of numbers.
private void WorkCompleted(object sender, RunWorkerCompletedEventArgs e)
// After work completed remove event handlers and dispose.
{
var bw = (BackgroundWorker)sender;
bw.RunWorkerCompleted -= WorkCompleted;
bw.DoWork -= DoWork; bw.Dispose();
}
private void DoWork(object sender, DoWorkEventArgs e)
{
var data = (List<int>)e.Argument;
foreach (var number in data)
{
if (progressBar1.InvokeRequired)
{
progressBar1.Invoke((MethodInvoker)delegate
{ this.ProcessNumber(number); });
}
else
{
ProcessNumber(number);
}
}
}
private void ProcessNumber(int i)
{
progressBar1.PerformStep();
//do something with i
}
Take a look at BackgroundWorker control. During form load invoke;
backgroundWorker.RunWorkerAsync();
and override event DoWork to do the dirty work (load data from database) and ProgressChanged to update progress bar. In the event body (lets say the event signature will be something like this):
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e) {
var worker = (BackgroundWorker)sender;
// time consuming operation
worker.ReportProgress(10, null);
// ... another stuff
}
private void backgroundWorker_ProgressChanged(object sender,
ProgressChangedEventArgs e)
{
progressBar.Value = e.ProgressPercentage;
}
I am using a background worker to update some tables in sqlserver. the progressbar max is getting set to the correct value, the progressbar value is being incremented, the backgroundworker progresschanged is being called correctly with correct value, yet the bar is not progressing.
here is the code for the form
in the background_dowork method there is a loop which calls updateProgressBarValue which works with correct values.
public InterfaceConvertLonLat()
{
InitializeComponent();
Shown += new EventHandler(Form1_Shown);
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);
backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
}
public void ConvertLonLat_Load(object sender, EventArgs e)
{
}
public void updateProgressBarValue()
{
progressBar1.Value++;
backgroundWorker1.ReportProgress(progressBar1.Value);
}
public void setProgressBarMax(int max)
{
progressBar1.Maximum = max;
MessageBox.Show("setprogressbarmax " + max);
}
public void Form1_Shown(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
}
public void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
convert.OSGB36ToWGS84("paf");
}
public void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
}
here is the loop contained in another class which calls updateprogressbarvalue, this is being fired and as stated backgroundworker1_progressChanged is being fired but the bar is not moving.
con.setProgressBarMax(address.Tables[0].Rows.Count);
foreach (DataRow LonLat in address.Tables[0].Rows)
{
con.updateProgressBarValue();
Double lon = 0;
Double lat = 0;
lat = Convert.ToDouble(LonLat["LTO"]);
lon = Convert.ToDouble(LonLat["LGO"]);
LocalToWGS84(ref lat, ref lon, OGB_M);
cmd1.Parameters["#LTW"].Value = lat;
cmd1.Parameters["#LGW"].Value = lon;
string dbQuery1 = "update " + tableName + " set LTW = #LTW, LGW = #LGW";
cmd1.CommandText = (dbQuery1);
cmd1.CommandType = CommandType.Text;
cmd1.Connection = conn;
cmd1.ExecuteNonQuery();
}
}
catch (Exception e)
{
MessageBox.Show("error converting: " + e.Message);
}
finally
{
conn.Close();
}
When reporting progress you need to fire an event from the background worker to tell the progressbar it needs updating. This can be done using the below:
backgroundWorker1.ReportProgress(10);
Change the value to what you need to demonstrate increased progress in your code. The progress changed event will mostly run on the same thread as your GUI so you should have no cross thread issues. One exception is if your form is being called from Excel via addin in which case Excel will be on the main thread.
You've got a number of issues - Your UpdateprogressBarValue() just increases the value of the progress bar but doesn't keep track of how many times its been called / what the current value is - so if you call it 101 times (assuming a range of 0-100), you'll get an OutOfRangeException
Your DoWork() method doesn't seem to call the update at all (either directly or by raising an event).
You can use events to do this but you're better off using delegates or perhaps anonymous functions. Something like...
public void setProgress(int value) {
progressBar1.invoke(delegate{ progressBar1.Value = value; }
}
then just call setProgress(0) through setProgress(progressBar1.MaxValue) from your DoWork() method
The following is seriously wrong:
public void updateProgressBarValue()
{
progressBar1.Value++; // not thread-safe
backgroundWorker1.ReportProgress(progressBar1.Value);
}
ReportProgress() is intended to be called form DoWork, it should not even read a Control property.
You should maintain a counter in the foreach loop and feed that to the progress mechanism.
Now this does not directly indicate why it doesn't move but you do not have a Completed handler. Are you sure the process finishes at all?
If an exception escapes your DoWork you will never know what happened.
private void Form1_Load(object sender, System.EventArgs e)
{
// Start the BackgroundWorker.
backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 1; i <= 100; i++)
{
// Wait 100 milliseconds.
Thread.Sleep(100);
// Report progress.
backgroundWorker1.ReportProgress(i);
}
}
private void backgroundWorker1_ProgressChanged(object sender, progress e)
{
// Change the value of the ProgressBar to the BackgroundWorker progress.
progressBar1.Value = e.ProgressPercentage;
// Set the text.
this.Text = e.ProgressPercentage.ToString();
}
I think this might be caused by the fact that the UI in windows forms is running in its own thread/context. In this case you probably need to use Invoke to make adjustments to the UI.
Something like this:
public void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
Invoke(new ProgressDelegate(UpdateProgress), e.ProgressPercentage);
}
delegate void ProgressDelegate(decimal value);
private void UpdateProgress(decimal value)
{
progressBar1.Value = value;
}