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.
Related
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.
When DataGrid (wpf) have many rows sorting can take quite long time (up to 5-10 seconds). How to change cursor to Cursors.Wait while searching?
I need somthing like this:
Xaml:
<DataGrid Name="List" SortStart="List_sortStart" SortComplete="sortComplete" />
Xaml.cs
void List_sortStart(object sender, EventArgs e) {
this.Cursor = Cursors.Wait;
}
void List_sortComplete(object sender, EventArgs e) {
this.Cursor = Cursors.Arrow;
}
But DataGrid does not have SortStart and SortComplete events.
The problem is with the WPF/rendering architecture lacking determinacy when processing user interface updates; in this case, a DataGrid sort change. The DataGrid sort operation is begun with a mouse click on the column, which then updates the CollectionView, which finally is rendered visible in the DataGrid at a point later in time. To implement a mouse cursor change as you wish, you need to change the cursor to your busy cursor at the start of the sort operation, and then defer the change back to the normal cursor to a point in time when the user interface context is finished with its final layout work. LUCKILY THIS IS POSSIBLE!
First, one needs a reference to the main rendering thread's dispatcher. A simple way to get it is to create a class-level data item (in the code-behind .CS file) which is initialized by the main user interface thread:
private static readonly Dispatcher UIDispatcher = Dispatcher.CurrentDispatcher;
Next, (in XAML) reference a code-behind handler for the DataGrid's Sorting event, which is executed whenever a sorting operation begins:
<DataGrid ... Sorting="DataGrid_Sorting">
The handler in the code-behind file looks like this:
private void DataGrid_Sorting(object sender, DataGridSortingEventArgs e)
{
Mouse.OverrideCursor = Cursors.Wait;
UIDispatcher.BeginInvoke((System.Action) (() => { Mouse.OverrideCursor = null; }),
DispatcherPriority.ContextIdle);
}
Note several things in the above code. First, when the sorting operation begins, we override the mouse cursor with the wait animation cursor on the first line. Next, we schedule code to execute on the user interface dispatcher with the DispatcherPriority.ContextIdle priority. This is the secret sauce that defers the code to change the mouse cursor back to normal.
The code to change the mouse cursor back to normal:
Mouse.OverrideCursor = null;
is only executed after the user interface dispatcher is finished with all of the processing of the sorting change/layout update logic and then becomes "idle".
There it is. This "trick" can be useful in many cases when coding for WPF. Put it in your quiver.
DataGrid has Sorting event which occur when the sorting is about to begin. You can attach List_sortStart method to this event.
But then the problem come, as far as I can find, DataGrid doesn't have event that occur when sorting completed. One possible way to workaround this limitation is by creating custom DataGrid with a kind of sorting completed event, see the example in this other SO post :
<local:DataGridExt Name="List" Sorting="List_sortStart" Sorted="List_sortComplete" />
In my WPF application, I have a single Main window with a Grid. The Login and Shell are 2 separate UserControls added as children to a grid. I need to find out when the Shell is loaded and start a timer from the Main window.
I just need to know as to what event is raised when a UserControl is added using Grid.Children.Add method, so that I can check if Login is loaded or the Shell and start the timer.
I'm not quite sure what you're trying,
but it sounds like you're looking for the Load event:
UserControl MyControl = new UserControl();
MyControl.Loaded += new RoutedEventHandler(MyControl_Loaded);
public void MyControl_Loaded(object sender, RoutedEventArgs e)
{
if (((UserControl)sender).IsLoaded)
{
..... do something
}
}
Hope it helps
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?
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 :)