In my C# WinForms app, I have the MainForm in which I am using a BackgroundWorker to read a large Text file.
I am also using a second Form to display a Marquee ProgressBar to inform user that they must wait until file has been completely read.
The problem I am having is the Form with the progressbar (SimpleProgressBar) is frozen until the file is read. Where the BW should be on a separate thread to not let this happen.
I left the code that reads the file as it may be relevant to my problem. However all the code actually works its just that the Form to display the ProgressBar is frozen.
SimpleProgressBar.cs (Form)
//Simple Progress Bar set to Marquee
public partial class SimpleProgressBar : Form
{
public SimpleProgressBar()
{
InitializeComponent();
//ProgressBar is setup in the designer.
/*
System.Windows.Forms.ProgressBar progressBar1;
this.progressBar1.Location = new System.Drawing.Point(16, 65);
this.progressBar1.Name = "progressBar1";
this.progressBar1.Size = new System.Drawing.Size(350, 23);
this.progressBar1.Style = System.Windows.Forms.ProgressBarStyle.Marquee;
this.progressBar1.TabIndex = 1;
*/
}
}
MainForm.cs (Form)
//Class variable
private SimpleProgressBar wait = new SimpleProgressBar();
private void generatePreview()
{
//Setup BW Thread
BackgroundWorker worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.WorkerSupportsCancellation = true;
worker.DoWork += worker_DoWork;
worker.ProgressChanged += worker_ProgressChanged;
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
//Start procesing file
worker.RunWorkerAsync();
//Show the dialog
wait.ShowDialog(); //perhaps .Show() would be better ?
}
//Once completed put the text into the textbox
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
textBox1.Text = ((StringBuilder)e.Result).ToString();
}
//Report progress here
void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker bw = (BackgroundWorker)sender;
int bufferSize = 1024;
var sb = new StringBuilder();
var buffer = new Char[bufferSize];
var length = 0L;
var totalRead = 0L;
var count = bufferSize;
using (var sr = new StreamReader("c:\200mb_text_file.txt"))
{
if (bw.CancellationPending)
{
}
else
{
length = sr.BaseStream.Length;
while (count > 0)
{
count = sr.Read(buffer, 0, bufferSize);
sb.Append(buffer, 0, count);
totalRead += count;
}
}
}
e.Result = sb;
}
UPDATE
So essentially I wanted to create a generic 2nd Form to use a a progress indicator that can be used for multiple purposes.
However it looks like that the BW MUST be on the same thread that hold the UI elements that need updating.
ShowDialog shows a modal dialog that you'll have to explicitly close. When you call ShowDialog, the program will not continue to execute beyond that point. Your background worker's completion event will have to close the dialog.
In addition, you're reading 1,024 bytes at a time and then calling the progress event. Every call to the progress event requires marshaling to the UI thread. It takes approximately zero time to read 1,024 bytes, which means that the progress event is being called continually, which in turn means that the UI thread is nearly 100% occupied with your progress update.
If you really need to report progress as the thing is loading, then use a larger buffer. You'll get better read performance anyway with a 64 Kilobyte buffer. That is:
int bufferSize = 65536;
But your files must be huge. You should be able to read at least 50 megabytes per second unless you have a really slow disk or you're reading over a slow network.
Progress Bar needs to be updated in another Thread.Updating the progress bar from the UI thread will cause freezing issues. Just put the code to update the progres bar in the Backgroudnworker's DOWork Method rather than reporting it.
void worker_DoWork(object sender, DoWorkEventArgs e)
{
stuffdone()
progressBar1.PerformStep();
}
Before using this you will need to set Form.CheckForIllegalCrossThreadCalls = false; so that BW can access the UI
Related
I have a method which is a time consuming one and therefore I have been trying to implement a BackgroundWorker, but it does not allow accessing UI controls which I have read and tried (hacking it) but to no avail.
What my method does: Creates a new BitmapImage, sets the source local or streamed (the parameter), writes it to a new WriteableBitmap, which is used for ConvertToGrayscale and then saves the BW Copy to IsolatedStorage in a folder.
So all this happens quite fast. But, only when I have say less than 25 Source Images. If I have about 100+ Images, this takes considerably long like 20 seconds or more and therefore, I would like to show a ProgressBar in the same PhoneApplicationPage but I have been struggling with how to not block the UI and show the ProgressBar while the method is doing its work.
This is the code that I have:
void GetImages()
{
if (!myIsolatedStorage.DirectoryExists("ImagesBW") && !_appsettings.Contains("_update"))
{
myIsolatedStorage.CreateDirectory("ImagesBW ");
for (int i = 0; i < coll.Desserts.Count; i++)
{
BitmapImage bmp = new BitmapImage();
bmp.CreateOptions = BitmapCreateOptions.None;
if (coll.Desserts[i].HasAssociatedImage)
{
bmp.SetSource(coll.Desserts[i].GetImage());
WriteableBitmap wb = new WriteableBitmap(bmp);
ConvertToGrayscale(wb);
BitmapImage bit = ConvertWBtoBI(wb);
SaveBWCopy(bi, i.ToString());
}
else
{
bmp.UriSource = new Uri("/Assets/Images/MissingArt.png", UriKind.Relative);
WriteableBitmap wb = new WriteableBitmap(bmp);
ConvertToGrayscale(wb);
BitmapImage bit = ConvertWBtoBI(wb);
SaveBWCopy(bi, i.ToString());
}
}
_appsettings["_firstLaunch"] = "false";
_appsettings.Save();
}
else if (myIsolatedStorage.DirectoryExists("ImagesBW ") && _appsettings.Contains("_update"))
{
string[] files = myIsolatedStorage.GetFileNames("ImagesBW/*");
for (int s = 0; s < files.Length; s++)
{
myIsolatedStorage.DeleteFile("ImagesBW/" + s + ".jpg");
}
myIsolatedStorage.DeleteDirectory("ImagesBW");
myIsolatedStorage.CreateDirectory("ImagesBW");
for (int i = 0; i < coll.Desserts.Count; i++)
{
BitmapImage bmp = new BitmapImage();
bmp.CreateOptions = BitmapCreateOptions.None;
if (coll.Desserts[i].HasAssociatedImage)
{
bmp.SetSource(coll.Desserts[i].GetImage());
WriteableBitmap wb = new WriteableBitmap(bmp);
ConvertToGrayscale(wb);
BitmapImage bit = ConvertWBtoBI(wb);
SaveBWCopy(bi, i.ToString());
}
else
{
bmp.UriSource = new Uri("/Assets/Images/MissingArt.png", UriKind.Relative);
WriteableBitmap wb = new WriteableBitmap(bmp);
ConvertToGrayscale(wb);
BitmapImage bit = ConvertWBtoBI(wb);
SaveBWCopy(bi, i.ToString());
}
}
_appsettings.Remove("_update");
_appsettings.Save();
}
btnStart.IsEnabled = true;
}
Backgroundorker is your best bet. There are huge amount of resources on the web how to implement it. But the idea behind is that Backgroundworker runs in a separate thread from the UI. You can access main thread UI by two methods, a delegate or through the Backgroundworker's ProgressChanged method, which gives access to you UI thread.
Lets say you have this.
public MainWindow()
{
InitializeComponent();
//if you want to disable you button you can do it here
BackgroundWorker _bw = new BackgroundWorker();
_bw.DoWork += new DoWorkEventHandler(_bw_DoWork);
_bw.WorkerReportsProgress = true;
_bw.ProgressChanged += new ProgressChangedEventHandler(_bw_ProgressChanged);
_bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_bw_RunWorkerCompleted);
_bw.RunWorkerAsync();
//or here
//Display progress bar here too
}
void _bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//this can give you access to the UI after work is completed
// to check that everything is ok or hide progress bar
}
void _bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
//this will give you access to the UI in the middle of you work like update progress bar
}
void _bw_DoWork(object sender, DoWorkEventArgs e)
{
//actual work here including GetImages
//work work work
GetImages();
}
In you getimages method after the SaveBWCopy you can add this to update the progress bar
_bw.ReportProgress(int progress )
//progress is the percentage you want to send to progress bar to display that is going to be in the e eventargument you passed.
I am trying to show a progress bar when I click a button, and hide it when the work is finished. But when I show the progress bar in the button event handler, it doesn't work and is only shown after the work is done.
Here is my code:
private void Button_Click_1(object sender, RoutedEventArgs e)
{
this.Dispatcher.BeginInvoke((Action)(() =>
{
loadingprgoress.Visibility = Visibility.Visible;
}));
int usresult, psresult;
con.Open();
SqlDataReader data = null;
String myQueryEdit1 = #"SELECT Username, Password FROM [dbo].[Table]";
com.CommandText = myQueryEdit1;
com.Connection = con;
data = com.ExecuteReader();
Random rnd = new Random();
int newrnd = rnd.Next(1, 100);
if (data.Read())
{
string userhash = GenerateHashWithSalt(data["Username"].ToString(), newrnd.ToString());
string passhash = GenerateHashWithSalt(data["Password"].ToString(), newrnd.ToString());
string userhash1 = GenerateHashWithSalt("admin", newrnd.ToString());
string passhash1 = GenerateHashWithSalt(pasbox.Password, newrnd.ToString());
usresult = userhash.CompareTo(userhash1);
psresult = passhash.ToString().CompareTo(passhash1);
if (usresult == 0 && psresult == 0)
{
con.Close();
dental_main_Window neww = new dental_main_Window();
neww.Show();
Close();
}
else
{
con.Close();
pasbox.Password = "";
}
Thread.Sleep(3000);
this.Dispatcher.BeginInvoke((Action)(() =>
{
loadingprgoress.Visibility = Visibility.Hidden;
}));
return;
}
}
This happens because your UI thread is doing all the work (accessing the database) and until it's done, it can't process any more messages (like showing the progress bar).
You need to do the opposite, show the progress bar in the UI thread without BeginInvoke and do the database accesss in another thread. When you the other thread finishes, you need to do a BeginInvoke on the UI thread to hide the progress bar.
Here is another important note: this.Dispatcher.BeginInvoke doesn't spawn a thread. It "pushes" the command to the queue of the UI thread because all UI operations must be executed from the UI thread.
Here is what you do:
private void Button_Click_1(object sender, RoutedEventArgs e)
Show progress bar
Use Tasks to start your background process
Add a continuation task to your first task to send the message to the UI that the process has completed
In the continuation task use this.Dispatcher.BeginInvoke so that the UI staff happens on the UI thread.
Have a look at background worker (http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx)
It allows you to run an operation on a separate, dedicated thread.
During the background computation, you can raise the ProgressChanged event to notify the UI and update the ProgressBar accordingly:
private BackgroundWorker _worker;
public Form1(string[] args)
{
InitializeComponent();
_worker = new BackgroundWorker();
_worker.WorkerReportsProgress = true;
_worker.RunWorkerCompleted += worker_WorkCompleted;
_worker.DoWork += worker_DoWork;
_worker.ProgressChanged += worker_ProgressChanged;
}
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
DoStuff();
}
private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
}
private void worker_WorkCompleted(object sender, RunWorkerCompletedEventArgs e)
{
_running = false;
UpdateUi();
}
private bool DoStuff()
{
//...
_worker.ReportProgress(20);
//...
_worker.ReportProgress(20);
return true;
}
private void btnUpdate_Click(object sender, EventArgs e)
{
_worker.RunWorkerAsync();
}
UI in WPF needs to execute on the UI thread. And you recognise that because I can see you using Dispatcher.BeginInvoke to interact with your progress window.
Your problem though is that you are executing your long-running task also on the UI thread. When you do that you don't give the progress window an opportunity to operate. The thread is consumed by the long-running task and is thus unable to service the UI.
The solution is to execute the long-running task away from the UI thread.
In my app I want create thousands files in background, but when I execute this it always freeze my form(after all files were created I can work with my form again). How run it properly without hanging?
private void button5_Click(object sender, EventArgs e)
{
BackgroundWorker bw = new BackgroundWorker();
bw.WorkerReportsProgress = true;
bw.DoWork += new DoWorkEventHandler(
delegate(object o, DoWorkEventArgs args)
{
BackgroundWorker b = o as BackgroundWorker;
this.Invoke(new MethodInvoker(delegate
{
getValues();//load some text fields into strings
while (counter < counted)
{
text = richTextBox1.Text;
text = text.Replace("number", finalNumber);
//create copies
if (checkBox1.Checked == true)
{
while (createdCopies < copies)
{
createdCopies++;
File.WriteAllText(fileName, text);
overalCounter++;
b.ReportProgress(overalCounter);
}
counter++;
createdCopies = 0;
}
//dont create copies
else
{
File.WriteAllText(fileName, text);
counter++;
overalCounter++;
b.ReportProgress(overalCounter);
}
//info about number of files created
label6.Text = "created " + overalCounter.ToString() + " files";
}
label1.Text = "success";
}));
});
if (bw.IsBusy != true)
{
bw.RunWorkerAsync();
}
bw.ProgressChanged += new ProgressChangedEventHandler(
delegate(object o, ProgressChangedEventArgs args)
{
this.Text = string.Format("{0}% Completed", args.ProgressPercentage);
});
}
this.Invoke runs the code inside on the UI thread, blocking any UI updates.
Since you run everything in the Invoke method everything will run on the UI thread.
Create a separate Invoke around each place where you modify your UI controls and leave the heavy work outside the Invokes.
Do the actual work in your first delegate. The second delegate this.Invoke(...) executes on the thread of the form (=main thread) and therefore blocks you UI.
I want to set a progress bar for my application.
I am using a background to download somefiles from my server.
It would be nice if I set a progress bar for that.
I know to use
ProgressBar.Value = 5;
and more.
but I want to use progress bar.'
I am using this code for progress bar
private void bgDownload_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.downloadProgressBar.Value = e.ProgressPercentage;
}
but its not working.
Can any one say how can I do it.
Thanks In Advance
If you're doing processing in the background, the UI will not update until its over.
So what you'll need to do is create a seperate thread which the processing will happen in, and within that thread update the progress bar. In the main thread don't do anything.
To update the progress bar from within another thread, you will need a delegate (or you'll get a runtime error).
Hope this can get you started.
You can try to use BackgroundWorker to download some files.
So backgroud download won't effect your Process Bar.
private void btnDownLoad_Click(System.Object sender,
System.EventArgs e)
{
// Start the asynchronous operation.
backgroundWorkerDownLoadFile.RunWorkerAsync();
}
private void backgroundWorkerDownLoadFile_DoWork(object sender,
DoWorkEventArgs e)
{
// Get the File in Server.
}
I assume you are using background worker component. So, you will have to call ReportProgress method of backgroundworker from its DoWork method which should update the progress bar. Please find below some reference on ReportProgress method from MSDN:
http://msdn.microsoft.com/en-us/library/ka89zff4.aspx
in addition to what JKhuang wrote, to alter the values in your progress bar you'll need to add
a delegate and a callback function
delegate void SetProgCallback(int newVal);
private void SetProgressbarValue(int newVal)
{
if (progbar.InvokeRequired)
{
SetProgCallback d = SetProgressbarValue;
Invoke(d, new object[] { newVal });
}
else
{
//insert your actual code here
}
}
Here is an example of how to do this.
public int ProgressBarPercentage { get; set; }
public string StatusMessage { get; set; }
public void StartDownload()
{
BackgroundWorker bw = new BackgroundWorker();
bw.WorkerReportsProgress = true;
bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
bw.RunWorkerAsync();
}
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// Run clean up code here once complete (ie make sure progress bar is at 100 percent....
}
void bw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker bw = sender as BackgroundWorker;
// Download files here
List<string> filestoget = new List<string>();
filestoget.Add("File1");
filestoget.Add("File2");
filestoget.Add("File3");
filestoget.Add("File4");
filestoget.Add("File5");
foreach (string file in filestoget)
{
// Get File
// Report output
int progress = 0; // add soemthing here to calculate your progress
bw.ReportProgress(progress, string.Format("File {0} downloaded", file));
}
}
void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
ProgressBarPercentage = e.ProgressPercentage;
StatusMessage = e.UserState.ToString();
}
I have written code to save an image which is generated by the application. The size of the image is around 32-35 MB. While saving the image to a BMB file, it is taking a long time, around 3-5 secs. For this purpose, I have used a background worker but when running the background worker, it shows an error like..."can't access the object as it is created on different thread".
Following is the code:
private void btnSaveDesign_Click(object sender, RoutedEventArgs e)
{
Microsoft.Win32.SaveFileDialog sfd = new Microsoft.Win32.SaveFileDialog();
sfd.Title = "Save design as...";
sfd.Filter = "BMP|*.bmp";
if (sfd.ShowDialog() == true)
{
ww = new winWait();
ww.Show();
System.ComponentModel.BackgroundWorker bw = new System.ComponentModel.BackgroundWorker();
bw.DoWork += new System.ComponentModel.DoWorkEventHandler(bw_DoWork);
bw.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
fName = sfd.FileName;
cache = new CachedBitmap((BitmapSource)imgOut.Source, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
bw.RunWorkerAsync();
}
}
void bw_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
ww.Close();
}
void bw_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
BmpBitmapEncoder encoder = new BmpBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(cache)); //here... it says cant access...
using (FileStream file = File.OpenWrite(fName))
{
encoder.Save(file);
}
}
I have declared "cache" as a global object. (A similar trick worked when I was programming in Windows Forms with VB.NET.)
ww is the wait window that I want to be displayed while the precess is being executed.
How to do this? Is there any other simple method for multi threading in WPF?
When WPF objects are created they are assigned to a Dispatcher object. This disallows any threads other than the creating thread to access the object. This can be circumvented by freezing the object by calling the freeze method. You would need to call Freeze on your bitmapsource object. Once you have frozen your object it becomes uneditable
Your problem comes about because you are accessing an object which is not created by the background worker thread. Normally this would happen if you access a UI control which is created in the main thread and accessed from different thread.
Use the code below.
Dispatcher.Invoke
(
new Action(
delegate()
{
BmpBitmapEncoder encoder = new BmpBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(cache));
using (FileStream file = File.OpenWrite(fName))
{
encoder.Save(file);
}
}
)
);
I think you have to pass cache as a parameter to the new thread:
bw.RunWorkerAsync(cache);
and get it from the DoWork method:
var cache=(CacheType) e.Argument;
.NET framework provides a simple way to get started in threading with
the BackgroundWorker component. This wraps much of the complexity and
makes spawning a background thread relatively safe. In addition, it
allows you to communicate between your background thread and your UI
thread without doing any special coding. You can use this component
with WinForms and WPF applications. The BackgroundWorker offers
several features which include spawning a background thread, the
ability to cancel the background process before it has completed, and
the chance to report the progress back to your UI.
public BackgroudWorker()
{
InitializeComponent();
backgroundWorker = ((BackgroundWorker)this.FindResource("backgroundWorker"));
}
private int DoSlowProcess(int iterations, BackgroundWorker worker, DoWorkEventArgs e)
{
int result = 0;
for (int i = 0; i <= iterations; i++)
{
if (worker != null)
{
if (worker.CancellationPending)
{
e.Cancel = true;
return result;
}
if (worker.WorkerReportsProgress)
{
int percentComplete =
(int)((float)i / (float)iterations * 100);
worker.ReportProgress(percentComplete);
}
}
Thread.Sleep(100);
result = i;
}
return result;
}
private void startButton_Click(object sender, RoutedEventArgs e)
{
int iterations = 0;
if (int.TryParse(inputBox.Text, out iterations))
{
backgroundWorker.RunWorkerAsync(iterations);
startButton.IsEnabled = false;
cancelButton.IsEnabled = true;
outputBox.Text = "";
}
}
private void cancelButton_Click(object sender, RoutedEventArgs e)
{
// TODO: Implement Cancel process
this.backgroundWorker.CancelAsync();
}
private void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
// e.Result = DoSlowProcess((int)e.Argument);
var bgw = sender as BackgroundWorker;
e.Result = DoSlowProcess((int)e.Argument, bgw, e);
}
private void BackgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
workerProgress.Value = e.ProgressPercentage;
}
private void BackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error != null)
{
MessageBox.Show(e.Error.Message);
}
else if (e.Cancelled)
{
outputBox.Text = "Canceled";
workerProgress.Value = 0;
}
else
{
outputBox.Text = e.Result.ToString();
workerProgress.Value = 0;
}
startButton.IsEnabled = true;
cancelButton.IsEnabled = false;
}