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?
Related
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 an OpenTK GLControl embedden in a WindowsFormsHost in my WPF application.
I want to continuously update and render it.
In Winforms a solution would be to attach the UpdateAndRender method to the Application.Idle event, but there is no such thing in WPF.
So what would be the best way to do (60FPS) updating of my scene and GLControl ?
You can use a System.Timers.Timer to control how often your render code is called. In your window containing the GLControl-in-WindowsFormsHost, declare a private System.Timers.Timer _timer;, then when you're ready to start the rendering loop, set the timer interval and it's event handler, then start it up, as in the following example:
private void btnLoadModel_Click(object sender, RoutedEventArgs e)
{
LoadModel(); // do whatever you need to do to prepare your scene for rendering
_timer = new System.Timers.Timer(10.0); // in milliseconds - you might have to play with this value to throttle your framerate depending on how involved your update and render code is
_timer.Elapsed += TimerElapsed;
_timer.Start();
}
private void TimerElapsed(object sender, ElapsedEventArgs e)
{
UpdateModel(); // this is where you'd do whatever you need to do to update your model per frame
// Invalidate will cause the Paint event on your GLControl to fire
_glControl.Invalidate(); // _glControl is obviously a private reference to the GLControl
}
You'll clearly need to add using System.Timers to your usings.
You can use Invalidate() for it. This causes the GLControl to redraw it's content.
If you call it at the end of Paint() you may blocking some UI rendering of the other WPF controls.
WPF provides a per frame render event: CompositionTarget.Rendering. This event is called before WPF wants to render the content. Subscribe from it and call Invalidate:
public YourConstructor()
{
//...
CompositionTarget.Rendering += CompositionTarget_Rendering;
}
void CompositionTarget_Rendering(object sender, EventArgs e)
{
_yourControl.Invalidate();
}
You need to unsubscribe if you don't use it anymore (to avoid memory leaks).
Here is a How to: Render on a Per Frame Interval Using CompositionTarget from MSDN.
I use GLControl with that method and it works fine. I did not checked how much FPS I have but it feels smooth.
You may also have a look on this: Why is Frame Rate in WPF Irregular and Not Limited To Monitor Refresh?
I know that there are several implementations here and there, but i was still not able to 'lock' on something really useful...
Whenever i set some component DataContext or ItemsSource to some big object, there is this 'render time frozen GUI' which make the app real annoying (even when using Virtualization).
I know i can iterate the object and set the items one by one and show progress, but i am looking for some other approach which can let me show some moving indication while GUI is rendering. I also prefer to have some progress bar and not only make the mouse cursor change.
Is there a decent way to achieve the followings?
Many Thanks
Zamboni example is a very good one, but still does not solve the frozen GUI problem.
As mentioned, there is no currently simple way of having something 'alive' to update a gui control while GUI is busy rendering.
I currently found some event that is 'alive and kicking' while gui is rendering, althogh it should be turned off when not needed as it can fire something like 60 times per second.
CompositionTarget.Rendering += ReportRenderProgress;
You can then implement ReportRenderProgress() anyway you like to signal you progress bar to update. Currently, i dont see any better solution available in WPF to update a progress indication while rendering so i am marking this as the answer.
This is actually a problem. You are using the GUI thread to fill the data (from object structure into GUI). The GUI thread is required both to read Windows message queue (prevent app from freezing, allow app to be moved/respond) and it is required to do any updates to the GUI.
One solution could be to slowly fill the the object structure after binding. This would have to be done from the GUI thread, so you could add DoEvents() and/or some percent indicator+forced refresh to make application seem alive.
I am interested to hear if anyone has a better solution though.
BackgroundWorker has everything you need.
EDIT
In WPF the Dispatcher is being employed automatically to invoke cross-thread method calls.
Check out Build More Responsive Apps With The Dispatcher in MSDN magazine.
I also put together some code fragments from a ViewModel that shows a BackgroundWorker updating a progress bar.
<ProgressBar
VerticalContentAlignment="Stretch" VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Minimum="0" Maximum="100"
Value="{Binding Path=BarPosition, Mode=TwoWay}"/>
// configure the background worker...
_backgroundWorker = new BackgroundWorker();
_backgroundWorker.WorkerReportsProgress = true;
_backgroundWorker.WorkerSupportsCancellation = true;
_backgroundWorker.DoWork += new DoWorkEventHandler(_backgroundWorker_DoWork);
_backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_backgroundWorker_RunWorkerCompleted);
_backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(_backgroundWorker_ProgressChanged);
// control progress bar position
private int _barPosition = 0;
public int BarPosition
{
get { return _barPosition; }
set
{
_barPosition = value;
OnPropertyChanged("BarPosition");
}
}
// long operation
void _backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker bw = sender as BackgroundWorker;
if (bw != null)
{
int pos;
for (int i = 0; i < 100; ++i
{
// report progress here for our long running operation..
pos = i/100;
bw.ReportProgress(pos);
Thread.Sleep(1000); // fake long operation
}
}
}
// report progress,,,
void _backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
BackgroundWorker bw = sender as BackgroundWorker;
if (bw != null)
{
BarPosition = e.ProgressPercentage;
}
}
// reset scroll bar position
void _backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
BackgroundWorker bw = sender as BackgroundWorker;
if (bw != null)
{
BarPosition = 0;
// Forcing the CommandManager to raise the RequerySuggested event to refresh UI...
CommandManager.InvalidateRequerySuggested();
}
}
The LoadIt() method below takes 5-10 seconds to complete.
I want the message area to display "Loading..." before LoadIt() starts and display "Reloaded" after it finishes.
How can I do that?
The following code doesn't work. It seems to not update the label until everything is finished, at which point it just displays "Reloaded" again.
private void Button_Click(object sender, RoutedEventArgs e)
{
lblMessage.Text = "Loading...";
LoadIt();
lblMessage.Text = "Reloaded";
}
There's more than one solution discussed here:
http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/6fce9b7b-4a13-4c8d-8c3e-562667851baa/
you could move LoadIt to a separate thread, or you could simulate the WinForms Application.DoEvents but this is quite a hack (http://shevaspace.spaces.live.com/blog/cns!FD9A0F1F8DD06954!526.entry)
You can use the Dispatcher object to start tasks in the background thread:
public delegate void LoadItDelegate();
this.Dispatcher.BeginInvoke(
System.Windows.Threading.DispatcherPriority.Background,
new LoadItDelegate(LoadIt));
Make sure its in the background, because the UI thread has more priority, so the UI gets updated.. and also move your "I am done message" to the end of your LoadIt method :)