Showing a WPF loading message until after UI finishes updating - c#

In my application, I'm allowing the user to select an item from a tree of items in the left side, and when that item gets selected, I should update the UI on the right side. Now, during the update of the UI (which might take a second or two), I display a "Please Wait" message to indicate that the application is currently working via a background thread.
When the loading of the UI is completed I wish to make this "Please Wait" window disappear, but the problem now is that the data fetching process ends quite fast, which makes the "Please Wait" window to disappear, but since the UI hasn't yet updated, the app gets stuck for a second and then, only after UI was updated, it gets released.
How can I send a message to remove the "Please Wait" message window only after UI has completed its work and refreshed? (I use GalaSoft's Messenger class to send messages through my application)
EDIT:
This is my code for reference:
// Show the "Please wait window" while running the long operation
var t = Task.Factory.StartNew(() => LoadingHelper.ShowProgressBar());
t.ContinueWith((priorTask) =>
{
// DOING SOME HEAVY LIFTING WORK HERE
// TRYING TO FORCE THE UI TO REFRESH
Application.Current.Dispatcher.Invoke(DispatcherPriority.Render, new Action(() => { }));
});
t.ContinueWith((antecedent) => LoadingHelper.HideProgressBar()); // Make the loading window disappear
EDIT2:
I'm aware of async programming. My question was mainly since my "heavy lifting" work is done and the "please wait" message is removed, but the UI is refreshing to reflect the results from the heavy work and that is taking a few moments. I want to keep the "Please Wait" message on the screen until all UI has done loading (maybe by using the Loaded event on the control?)

Instead of forcing a render, why not try to wait for rendering to finish and dispatch?
Maybe get rid of the line, and do
Application.Current.Dispatcher.Invoke(new Action(() => LoadingHelper.HideProgressBar()), DispatcherPriority.ContextIdle, null)
at the exact line you are trying a force render?

What you're looking for is Asynchronous Programming
Have a read of this http://msdn.microsoft.com/en-us/library/hh191443.aspx
This helps with this exact situation where you do not want to block the UI (and thus a bad reflection on the UI for the user), but need work doing in the background.
There are some usable examples in that article.
Create your Task to do the heavy work.
Create an async void method to call the task and await it.
Before you await the task, show your busy message > await the task > when the task is complete remove the busy message.
The important thing here is that you are taking away the load on the UI thread and giving it to another. At the moment is sounds like your UI thread is doing more processing than it needs to.

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 to make UI changes while loading next screen in Xamarin.Forms

I have a screen that takes some time to load. All I want to do is display a simple "Now Loading" message before the next screen loads. How do I achieve this? Here is the code I currently have trying to accomplish this:
void BT_User_Clicked(object sender, EventArgs e)
{
// Start a new task (this launches a new thread)
var t = Task.Run(() =>
{
// Do some work on a background thread, allowing the UI to remain responsive
Navigation.PushAsync(new UserList());
});
BT_User.IsEnabled = false;
BT_User.Text = "Now Loading";
}
When running this code it Almost achieves what I want. It displays the "Now Loading" text, and in the background, it calls the next screen(I can see this only by setting a breakpoint in the load of the next screen). Problem is, the next screen never shows itself. I'm guessing this is because it is loaded on a background thread but not the main UI thread.
If you throw in a new Screen from a background thread, it won't work. Changes in the UI need to be done in the Main UI Thread.
However Xamarin.Forms provides you with a helper:
In your background thread simply call:
var t = Task.Run(() =>
{
// Do some work on a background thread, allowing the UI to remain responsive
Xamarin.Forms.Device.BeginInvokeOnMainThread(() => {
Navigation.PushAsync(new UserList());
});
});
Regarding your Loading overlay, you either need a plugin capable of handling popup dialogs (for instance Acr.UserDialogs) or you need to implement your own dialog system, if you don't want to be dependent on third party libraries.
I already have done the latter and provided a brief explanation on how to do it over here:
Display a popup with xamarin forms

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.

Unreliable GUI update in multithreaded C# program

I have a program that runs a series of methods in other threads within one window and let's the user know what's going on using a status bar. The status bar updates are in the main thread which set's the status bar and then refreshes the GUI. There are several blocks of code in series each looking something like this:
Thread do1Thread = new Thread(Class.Method);
do1Thread.Start();
// inform user
this.status.Text = "Doing stuff 1...";
// update GUI
Utility.RefreshGUI();
// join thread
do1Thread.Join();
Sometimes the status bar does indeed update but often is stays on the first status until the end when it displays the last status. Occasionally is sticks on "Ready." which is the default.
Note that two of the blocks take a few seconds so there should be time for it to update. Also, the program is written in C# (Mono) using GTK# for the GUI.
How can I ensure that that the GUI updates to reflect the change?
The problem is that the Join() call blocks the UI thread which blocks all window messages.
Can you use a BackgroundWorker and execute whatever code you have after the Join in the RunWorkerCompleted call?
You need to dispatch Update message to UI thread, call invoke instead of direct property
this.status.BeginInvoke((Action)(() => this.status.Text = "Something happen"));
The best way I have found to update a control in a primary thread is to set a delegate for updating and invoke that from other threads.
You have to use observe and observable pattern.
EDITED:
It is really better divide logic and view parts of code.
Here is an example in real world how to use. Pattern
Could you check whether you are using a StatusStrip control?
If so, your code looks like setting directly the Text of Stautus Strip Control
this.status.Text = "Doing stuff 1...";
So it wont reflect in the Status Strip as Text. You have to place a toolstriplabel and need to set its text in this case.
Please check the post here

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.

Categories