I have a task that takes a long time to execute. In order to inform the user of the progress, I have a progress bar that I update inside DoWork.
Can anybody tell me if this is the best way to update the progress bar? I have heard that there is a ReportProgress event handler but I am confused because I'm unsure of the purpose of ReportProgress.
Since the Background worker works in a separate thread, you'll run into problems if you try to access UI objects. Calling the ReportProgress method on the worker from inside the DoWork handler raises the ProgressChanged event. That event should be handled in the UI thread so as to easily access the control.
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += DoWorkHandler;
worker.WorkerReportsProgress = true;
worker.ProgressChanged += (s, e) =>
{ myProgressBar.Value = e.ProgressPercentage; };
worker.RunWorkerAsync();
...
public void DoWorkHandler(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
while (working)
{
// Do Stuff
worker.ReportProgress(progressPercentage);
}
}
The ProgressChanged event is what you are looking for. However, make sure you create the BackgroundWorker like below so it actually raises this event when ReportProgress is called.
BackgroundWorker bw = new BackgroundWorker() { WorkerReportsProgress = true };
bw.ProgressChanged += ... ;
ReportProgress is what you would use to update the progress of your task, including things like the UI--in your case, a proggress bar.
You should check out the MSDN docs, located here.
basically, you create a handler for the ReportProgress event, then in your DoWorkEventHandler, you call the ReportProgress like so:
worker.ReportProgress((i * 10));
Related
I'm writing a 3D wpf application using Viewport3D. When user push a button, I must start DoubleAnimation on AxisAngleRotation3D, but it must be done synchronous. I can't do it on animation.Completed, because this animation run the next one and the next one recursively.
ButtonHandler must work on UI thread, because to calculate animation I hardly use Viewport3D.
So I must in UI thread start synchronous animation and after it finished continoue work.
I tried this code, but it cause deadlock:
AxisAngleRotation3D axis;
DoubleAnimation animation;
DependencyProperty dp;
var worker = new Thread(() =>
{
Dispatcher.CurrentDispatcher.InvokeShutdown();
Dispatcher.Run();
});
worker.SetApartmentState(ApartmentState.STA);
worker.Start();
AutoResetEvent animationDone = new AutoResetEvent(false);
EventHandler handler = null;
handler = (sender, args) =>
{
animation.Completed -= handler; // remove self
animationDone.Set(); // signal completion
};
animation.Completed += handler;
Dispatcher.CurrentDispatcher.Invoke(new Action(() =>
{
axis.BeginAnimation(dp, animation);
}));
animationDone.WaitOne();
Ok, it's just an idea. Why don't you create this animations in few steps. First start first batch of animation and then, when they'll finish run another set of animations. You could synchronize it with some kind of timer.
You can first create list of animations and objects to run in each step and then just invoke them.
I have a WPF utility tool that has a lot of processing to do. The processing is done from MainWindow.xaml which utilizes a BackgroundWorker to execute. What I want to do is open a new separate window called Logger.xaml and update a TextBox found on the Logger.xaml window on the progress of MainWindow's execution. The purpose of this is so that a user can continue to select more processing to do from MainWindow.xaml while still getting progress updates of ongoing work. So in layman's terms: 1 MainWindow to multiple Logger windows. Here is what I have so far.
MainWindow.xaml.cs:
private void btn_Click(object sender, RoutedEventArgs e)
{
bw = new BackgroundWorker();
bw.WorkerReportsProgress = true;
bw.DoWork += (a, b) => WorkerMethod(x, y); //WorkerMethod does all the heavy lifting
bw.RunWorkerAsync();
}
private void WorkerMethod(string x, string y)
{
/***some work 1 code***/
//Somehow open `Logger.xaml` and update its textbox that some work 1 finished.
/***some work 2 code***/
//Update its textbox that Work 2 finished.
etc...
}
Should I use BackgroundWorker.ProgressChanged somehow to open up a new Logger window on another thread and update the TextBox in that window? What is the best way to achieve the desired effect?
Should I use BackgroundWorker.ProgressChanged somehow to open up a new
Logger window on another thread and update the TextBox in that window?
Yes, BackgroundWorker raises ProgressChanged events on the current SynchronizationContext of the thread that called RunWorkerAsync()..
To receive notifications of progress updates, handle the ProgressChanged event. To receive a notification when the operation is completed, handle the RunWorkerCompleted event.
What is the best way to achieve the desired effect?
If you want to use a BackgroundWorker, probably this is the best.
The gist of the code is
Storyboard story = new Storyboard();
DoubleAnimation anim = new DoubleAnimation();
anim.Completed += anim_Completed(object sender, EventArgs e);
...
story.Children.Add(anim);
story.Completed += story_Completed(object sender, EventArgs e);
story.Begin(control, true);
return;
In another method I have:
// Finish the Storyboard now
story.SkipToFill(control);
// I want it to get back to me here after the Completed events have run.
The problem is that the Completed events don't run until the next pass of the WPF dispatcher message loop which is no good to me because they update some state. I also tried
story.Stop(control);
but then the Completed handlers don't get run at all it seems. Is there a way to get the Completed handlers to fire immediately?
To avoid running your storyboard on another ui thread and handling locks or signals you can try the following:
Action emptyDelegate = delegate() { };
control.Dispatcher.Invoke(DispatcherPriority.Render, emptyDelegate);
...to force a render pass and hence pick up the completed events that don't seem to fire when you need them to during processing in the UI's code-behind.
You may see some artefacts come into and out of existence though as any updated dependency property values will, of course, be rendered.
I have a task that takes a long time. I do it with a background worker thread and before start it, since Do_Work I begin an animation over a label and when task finishes, I stop it in RunWorkerCompleted but I received an error because I try to begin/stop animation in the background thread that is not the owner. How can I do this, I mean to begin/stop animation in the background worker?
thanks!
You should start the animation before starting the BackgroundWorker, not in the DoWork event. That way, you will be able to stop it from the RunWorkerCompleted event.
You need to use the Dispatcher.BeginInvoke method, on the control which is doing the animation.
You can also call the stop animation on the UI thread using something like the following:
private void OnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
/* If not operating on the main UI thread, call this method again on the App dispatcher's thread */
if (App.Current != null && App.Current.Dispatcher.Thread != Thread.CurrentThread)
{
App.Current.Dispatcher.Invoke(new RunWorkerCompletedEventHandler(OnRunWorkerCompleted), new object[] { sender, e});
return;
}
// Do stuff to the UI here
}
I'm trying to send two events to the main window so that I can show some kind of animation that will let the user know that I'm updating the data.
This is an ObservableCollection object so the OnPropertyChanged is immediately picked up by the bindings on the main window. The sleep is only in there so that the user can see the animation.
However, the first OnPropetyChanged is never seen. I'm assuming this is because we're in a single thread here and the timer_Tick has to finish before the GUI updates. Any suggetions? In VB6 land we would use a DoEvents or a Form.Refresh.
Thanks!
private void timer_Tick(object sender, EventArgs e)
{
Loading = "Before: " + DateTime.Now.ToString();
OnPropertyChanged("Loading");
LoadData();
Thread.Sleep(1000);
//Loading = Visibility.Hidden;
Loading = "After: " + DateTime.Now.ToString();
OnPropertyChanged("Loading");
}
Well C# has an Application.DoEvents but I wouldn't recommend using it unless absolutely necessary.
Have you tried wrapping your OnPropertyChanged call in a Dispatcher.Invoke?