Wait until UIElement is drawn (without updatelayout) - c#

I have a function that takes around 5 seconds to complete if a button is clicked. If the button is clicked, I want to display some kind of notification to indicate that the buttonclick is being processed, something like
<Button Click="OnButtonClick" Content="Process Input" />
<Border x:Name="NotificationBorder" Opacity="0" IsHitTestVisible="False"
Width="500" Height="100" Background="White">
<TextBlock Text="Your input is being processed" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
And in code-behind on the button click:
private void OnButtonClick(Object sender, RoutedEventArgs e)
{
DoubleAnimation da = new DoubleAnimation
{
From = 5,
To = 0,
Duration = TimeSpan.FromSeconds(2.5),
};
// Making border visible in hopes that it's drawn before animation kicks in
NotificationBorder.Opacity = 1;
da.Completed += (o, args) => NotificationBorder.Opacity = 0;
NotificationBorder.UpdateLayout(); //Doesn't do anything
UpdateLayout(); // Doesn't do anything
NotificationBorder.BeginAnimation(OpacityProperty, da);
// Simulate calculationheavy functioncall
Thread.Sleep(5000);
}
Somehow UpdateLayout() isn't rendering fast enough, the notification is only displayed after the 5 seconds of Thread.Sleep are over.
Dispatcher.Invoke((Action)(() => NotificationBorder.Opacity = 1), DispatcherPriority.Render); won't work either.
Additionally, I can't let Thread.Sleep run in a separate worker thread - In the real application, it needs to read data from Dispatcher-owned objects and (re)build parts of the UI.
Is there a way to make it visible before Thread.Sleep() is called?

The problem is any long activity on main thread (UI thread) will lead freeze in UI, so you will not see any animation.
You need to do your calculations in a separate thread. I you need to access to Dispatcher-owned objects you can use uiObject.Dispatcher.BeginInvoke or Invoke. BackgroundWorker can help you the way to execute some long calculations and subscribe to the event Completed which will be fired by BackgroundWorker on the UI thread automatically.
If you are ok with UI freeze, change some property and other stuff re-schedule with Dispatcher.BeginInvoke(DispatcherPriority.Background,...), like
NotificationBorder.Opacity = 1;
Dispatcher.BeginInvoke(DispatcherPriority.Background, (Action)(()=>Thread.Sleep(5000)));

If you want the UI to update, you need to change your long-running operation to be asynchronous, which means properly decoupling the UI dependencies from whatever logic or perhaps IO-bound operations are taking all the time. If you don't do this, none of the UI updates occurring in your long-running operation will actually be reflected in the UI until it's done, anyway.
If you're running with .NET 4.5, here is my preferred pattern for how to do this:
public async Task DoComplexStuff()
{
//Grab values from UI objects that must be accessed on UI thread
//Run some calculations in the background that don't depend on the UI
var result = await Task.Run((Func<string>)DoComplexStuffInBackground);
//Update UI based on results
//Possibly do more stuff in the background, etc.
}
private string DoComplexStuffInBackground()
{
return "Stuff";
}
This is somewhat 'backwards' from the older style BackgroundWorker approach, but I find it tends to result in cleaner logic. Instead of writing your background code, with explicit Invokes for UI updates, you write your UI code around explicit background work calls via Task.Run(). This also tends to lead to a natural separation between the UI-centric code and the underlying logic code, as you need to refactor your logic into methods that can be called in this way. That's a good idea for long-term maintainability anyway.

So you can dispatch both action (UI and your calculations) into the dispatcher and they will be executed in a line
Dispatcher.BeginInvoke(do ui things);
Dispatcher.BeginInvoke(do your logic things);
But you'd better move your calculation into the background thread.

Related

UWP ProgressRing is shown with big lag during data loading in background

I'm using UWP GridView with DataTemplateSelector to show data for different weeks. When I change the week I want to show loader when data is loading. I'm using MvvmLight for ViewModels binding and when I change the data I'm removing and adding elements to the GridView source. The problem is that when I change IsActive property to true before I run UpdateGrid method, the loader is not active and there is a lag on screen. If data loading (UpdateGrid method) takes more than one sec the loader is visible, so it means for me that the logic there is ok, but the problem can be with generating graphical elements on the screen and performance?
I was trying to make my UpdateGrid method async and sync (there is no api call inside, so can be sync). The method is called in the ViewModel class:
DispatcherHelper.CheckBeginInvokeOnUI(async () =>
{
SyncLoadingImageVisible = true;
await UpdateGrid();
SyncLoadingImageVisible = false;
});
You may be misunderstanding the way async/await works. When you mark a method async ans it contains no real await (meaning no I/O bound operation or operation that actually takes place on another thread), the whole method will essentially run synchronously. This is true in your case as well as you mentioned there is no actual async work inside UpdateGrid so the code will work as if there was really no await.
The UI thread will be busy all the time from the moment you set the SyncLoadingImageVisible to true to the moment you set it back to false - during that time UI thread is 100% dedicated to executing your code so user won't see any UI changes. This causes the behavior you are seeing - that there is a lag as the UI thread does not have a chance to update the UI until the UpdateGrid method finishes executing.
To solve this properly you will have to offload performance intensive, non-UI tasks in UpdateGrid method to another thread using awaited Task.Run and only the code that really does work with the app's UI should then be executed on UI thread. This way you will free the UI thread to be able to display progress to the user while the execution runs in the background.

How can Load my UserControl using different threads?

I'm using .NET C# and trying to load a UserControl that contains three tabs. What I want to do is, to load the first Tab make it visible and responsive and then load the other two tabs in a background thread (or worker or task).
I've tried to do so using a thread, a task, a backgroundworker but always have the same exception "The calling thread cannot access this object because a different thread owns it."
here some code samples I used:
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.RunWorkerAsync();
in the bw_DoWork I put all the work I need to do that involves UI updates
Task t1 = new Task(DoWork);
t1.Start();
in the DoWork I do the same( some code with UI update)
Thanks for the help.
Oussema
This is not impossible, but it is a bad idea - it's very tricky to get right.
Instead, you want to separate the business logic from the UI, so that you can do the logic in the background, while the UI is still on the UI thread. The key is that you must not modify the UI controls from the background thread - instead, you first load whatever data you need, and then either use Invoke or ReportProgress to marshal it back on the UI thread, where you can update the UI.
If creating the controls themselves is taking too long, you might want to look at some options at increasing the performance there. For example, using BeginUpdate and friends to avoid unnecessary redraws and layouting while you're still filling in the data, or creating the controls on demand (as they are about to be visible), rather than all upfront.
A simple example using await (using Windows Forms, but the basic idea works everywhere):
tabSlow.Enabled = false;
try
{
var data = await Database.LoadDataAsync();
dataGrid.DataSource = data;
}
finally
{
tabSlow.Enabled = true;
}
The database operation is done in the background, and when it's done, the UI update is performed back on the UI thread. If you need to do some expensive calculations, rather than I/O, you'd simply use await Task.Run(...) instead - just make sure that the task itself doesn't update any UI; it should just return the data you need to update the UI.
WPF does not allow you to change UI from the background thread. Only ONE SINGLE thread can handle UI thread. Instead, you should calculate your data in the background thread, and then call Application.Current.Dispatcher.Invoke(...) method to update the UI. For example:
Application.Current.Dispatcher.Invoke(() => textBoxTab2.Text = "changing UI from the background thread");

ToolStripStatusLabel not behaving like other typelike controls

Im having some trouble with a ToolStripStatusLabel in Winforms application. To better explain i have some code here
bottomLbl.Text = "Adding file(s) to list...";
this.Text = "Adding file(s) to list...";
listAllFiles(carrier, type, chkListBox, withDestSystem, listBox, cmbBox);
bottomLbl.Text = "Done!";
this.Text = "Done";
What i dont get is, that this.Text does change to "Adding files.." but not bottomLbl even though i set it to do so before this.text. Both controls get the "Done!" text after the listAllFiles function has been run.
But is there something special i have to do on a ToolStripStatusLabel ?
You need to refresh the form before calling the function
bottomLbl.Text = "Adding file(s) to list...";
this.Text = "Adding file(s) to list...";
this.Refresh();
listAllFiles(carrier, type, chkListBox, withDestSystem, listBox, cmbBox);
bottomLbl.Text = "Done!";
this.Text = "Done";
It looks to me like you're setting up a condition where we can't reliably predict which of the UI updates will be displayed to the screen before the final UI updates get displayed.
You're doing some UI updates (e.g. label1.Text = ...) before performing a time-consuming process (listAllFiles()). After that completes, you're doing some more (final) UI updates.
One or more of the "early" UI updates gets queued to be displayed as soon as the main thread (the UI thread) has some time to refresh the window.
What we don't necessarily know is which types of controls (ToolStripStatusLabel versus Label, for instance) internally consider the setting of their Text property to be something that can't wait to be displayed.
It sort of sounds like one of those types will allow its Text property to be updated without waiting until it gets displayed (that's a non-blocking call) while the other type will ensure that the change gets displayed before proceeding (that's a blocking call).
Even if you determine what their behavior is through testing, it could change in the future or in different environments.
Optionally, you could customize one or more of those types to specify whether to block or not. You could do this by either calling Refresh() or Invalidate(). But I recommend an alternative approach:
Keep the load of tasks performed in the UI thread to a minimum. This way, the UI is more responsive to the user and you can have more confidence that your UI updates will be displayed quickly.
Generally, this means we should perform all time-consuming work in a different thread, asynchronously, and we can update the UI with the status of that work as frequently as we like. To accomplish this, I recommend looking into the BackgroundWorker type. Its purpose is to perform time-consuming work asynchronously while maintaining a responsive UI.

C# - Cross-thread operation - Create Control in thread, add to main form

I have an older form that I really don't want to rewrite at this point, so what I'm doing is loading the form and then adding it to a panel in the new UI form. This is working fine, but it's slow. The old form does a lot of loading and gathering of data and it's not very efficient. So as a result larger records take up to 30 seconds to load. As you know, creating the form then "locks up" the main UI for about 30 seconds while it loads the old form. This is the action I'm trying to prevent. I want to load the new form, display a "Loading" gif in the blank panel, and then once the old form is loaded remove the "Loading" image and add the form as a control.
And here starts the problem.
I've tried creating a Background Worker but this causes a STA error (old form has a few threaded data loadings of it's own), and since I can't change the worker to STA I stopped trying.
I've tried to create an Invoke (and BeginInvoke) and while this works, it doesn't really load the old form in the thread. It simply sends it back to the UI thread and does the work there. Again this hangs the UI. I.E.: Not what I want.
I've tried to create a delegate and trigger it as an event in the thread, but I get the same results as below...
I've created a thread, set STA on it, started it and then did a while loop with a DoEvents waiting on it to finish. Of course this all seems to work up to the point of accually adding the form to the panel, and then I get the "Control 'ChartForm' accesses from a thread other than the thread it was created on". In this error 'ChartForm' is the old chart that was loaded in the thread.
I've tried the above method, but I instead used a private static field to hold the creating of the old form, and then adding it to the panel once the thread is completed. This is in the method that created the thread, just after the while loop. Same error.
So, I've used the above method in other places with DataTables and didn't have any issue getting the data back to the main thread for use with DataBinding. I know that this is a little different but I didn't think that it would be this hard to do.
Below is the code that I have tried to use that seems to be the closest to what I want.
private static _ChartForm;
private void LoadPatientChart()
{
ClearMainPanel(); // Removes any loaded ChartForms from Panel
if (_Patient == null) // Test to make sure a patient is loaded
return;
loadingPanel.Visible = true; // Displays the "Loading" gif
Thread thread = new Thread(new ThreadStart(this.GetChartForm));
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
while (thread.ThreadState != ThreadState.Stopped)
Application.DoEvents(); // Keeps the UI active and waits for the form to load
this.ChartPanel.Controls.Add(_ChartForm); // This is where the error is
loadingPanel.Visible = false; // Hide the "Loading" gif
}
private void GetChartForm()
{
ChartForm chartForm = new ChartForm(_Patient.AcctNum.ToString(), false);
chartForm.TopLevel = false;
chartForm.FormBorderStyle = FormBorderStyle.None;
chartForm.Dock = DockStyle.Fill;
chartForm.Visible = true;
_ChartForm = chartForm;
}
It's really not a good idea to create UI controls on any other thread than the UI thread. It is technically possible, but it's difficult to manage, especially if the new thread is a "temporary" one.
What you really need to do is refactor out the work that the ChartForm is doing (on construction it appears?) and do that work on a background thread, and then return it to your UI thread and then create your ChartForm passing in the results of that work. IMHO this is a better design anyways; although it may be a lot of work for you.
I don't think what you want is possible without refactoring this "old form". There is only one UI thread, and all UI elements must be created on that thread to be displayed to the user.
I would suggest refactoring the form to display initially without any data (or maybe with a loading image), and then have the form start a background task using BackgroundWorker to perform the long running tasks that are not UI related (going to a database, etc.) Once the worker is complete, then you can run the code that initializes the Form's data elements. This will keep the UI responsive for as long as possible while the blocking tasks are performed.
I've tried to create an Invoke (and BeginInvoke) and while this works,
it doesn't really load the old form in the thread. It simply sends it
back to the UI thread and does the work there. Again this hangs the
UI. I.E.: Not what I want.
You must update the user interface on the main thread, you do not have any choice, if its still hanging then your doing the calculations in the wrong thread.

Blocking a Thread in .Net

I have a class that has purely static Methods and properties. I am calling an async method on the class "Load" that asks a web service for a chunk of data, which then fires an event that executes the return method, "LoadCompleted". I have no idea how long the call is going to take (the difference between calling the "Load" method, then the "LoadCompleted" getting called).
I would like to block the application from proceeding any further until the callback method has been raised (as the app will try and get stuff from this class, which isn't populated until the "LoadComplete" method sets the data).
How would i go about doing this?
Blocking the main UI thread should be avoided with extreme prejudice.
I would use the BusyIndicator control from the Silverlight Toolkit:-
<UserControl x:Class="StackoverflowSpikes.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:toolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit">
<toolkit:BusyIndicator x:Name="busyInd" >
<Grid x:Name="LayoutRoot">
<!-- The rest of your content -->
</Grid>
</toolkit:BusyIndicator>
</UserControl>
Before calling Load use:-
busyInd.IsBusy = true;
then on LoadComplete use:-
busyInd.IsBusy = false;
This will lock out user input on the UI without blocking the main thread and give the use some feedback as to why they can't click anything right now. You can supply your own content for the busy message using the BustContent property. Of course if you don't like the way it looks you can style it to your preference.
If you want to get all MVVM you can bind the IsBusy property to a VM property that indicates that the VM doesn't want anything changing right now.
You can use the ManualResetEvent class to block the main thread if you want. Just call the WaitOne method to block and call the Set method to unblock when the asyc web request completes. Just be aware that if you block your main UI thread, your entire application will become completely unresponsive.
You could consider setting the UI controls to disabled at the start. On load complete you could display your data and then enable the UI controls. No thread blocking is necessary with this approach.

Categories