I have a button that opens a second window but the second window has a bit of processing to do before displaying. Rather than have the initial window die until the second screen appears I would like to inform the user that processing is happening. My thoughts on how to do this;
1) Call the second window in a second thread, have the UI in the first window display "Loading" etc, once the second window has completed pass an indicator to the first window to stop displaying "Loading".
I have read a bit about dispatcher & beginInvoke but still struggling a bit to get this to work. I was trying this out and in my Window1 initializer I have a thread.Sleep just to test out that my mainWindow is still working but it is not. Any help would be greatly appreciated.
You cannot open windows and do other UI tasks on worker threads. All these activities have to be performed on the main thread. You can do that by invoking on the dispatcher from the background thread:
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
// First do the work
Thread.Sleep(...);
// Then show the window
Application.Current.Dispatcher.Invoke(() => window1.Show());
}
Using this simple approach where you grab the dispatcher in event handlers can quickly get out of hand and as your application grows you might want to switch to a more strict programming model using for instance a MVVM framework of sorts.
From a UX perspective it might be better to show the second window immediately and then display a progress bar or spinner indicating that work is being performed. Then when the work is done hide this indicator and show the result of the work. The principle of updating the UI from the background thread using the dispatcher still applies.
Related
In my c# winform app I have a button which when clicked, it performs some calculations which can tke some time.
I also have a label.visible = false which I would like to label.visible = true right after the button click so that the user can see the app working away.
The thing is even when label.visible = true is the first thing in the button1_Click(object sender, EventArgs e) method, it only toggles to visible at the very end, after the calculation is performed.
How can I show the label right after the button_click?
If you need your application to remain responsive whilst your app is processing, take a look at Background Worker
You can force your form to update & process any background messages by calling:
Application.DoEvents();
Just after you have changed your label - although this is probably a bit of a hacky solution.
Your calculation is being performed on the UI thread. This means that it is blocking the UI from refresshing after you have set the visibility of the label.
You should consider doing the calculation on another thread using a Task. This will allow the UI to be responsive during what it seems is a long operation that can run in the background.
e.g.
var taskCalc = Task.Factory.StartNew(() => //Do Calculation );
There are several ways to tackle this.
One way (the simplest) is to call
this.Refresh();
right after you set the Label to visible.
Another is to do your calculations in a background thread. The easiest way to do that is to use a BackgroundWorker. Then your main thread can just continue serving the UI (refreshing the form, responding to buttons etc.) while the background worker thread performs the computation.
See http://www.dotnetperls.com/backgroundworker for more on background worker threads.
Like others have said: You are performing your work on the UI Thread. I am showing you another valid way of achieving what you need. You can move your work to a separate thread using an anonymous delegate.
(new System.Threading.Thread(() =>
{
dowork(); // What ever work you need put here.
})).Start();
I want to have a loading form showing up while the main app connects to db and fetching initial data. Because this can take up 5-10 secs I want to inform the user that something is happening.
This is what I have come up with:
frmStart _frmStart = new frmStart();
Thread t = new Thread(() => _frmStart.ShowDialog());
t.Start();
objDAL = new DBManager();
objDAL.conSTR = Properties.Settings.Default.conDEFAULT;
PrepareForm();
t.Abort();
Is this the best way?
No, this doesn't solve the frozen UI problem, it merely papers it over. Imperfectly at that, the dialog's parent is the desktop and can easily appear underneath the frozen window and thus not be visible at all. And the dialog isn't modal to the regular windows since it runs on another thread. The failure mode when the user starts clicking on the non-responsive window is very ugly, those clicks all get dispatched when the UI thread comes back alive.
There are workarounds for that (you'd have to pinvoke SetParent and disable the main windows yourself) but that's just solving the wrong problem. You should never let the UI thread block for more than a second. Use a BackgroundWorker to do the heavy lifting, update the form with the query results in the RunWorkerCompleted event handler.
If you are using WinForms you may want to take a look at this A Pretty Good Splash Screen in C#. If you are using WPF you could take a look at this Implement Splash Screen with WPF.
I have a main form that contains an edit control that occupies the entire form. There is another worker thread that constantly writes log messages to this edit control. Now I want to show a dialog box with just a cancel button while the main UI's edit control is displaying stuff. The problem is that the cancel dialog is non-responsive while the updates are happening behind it and I cannot click on the cancel button. Any idea on how to resolve it. I was thinking of creating another UI thread and show the cancel button from it. Any other alternatives?
EDIT#1
I should clarify that I already use a worker thread to do the work.
DisplayLogs() is in a seperate thread.
DisplayLogs() is called from other threads.
LogMessage is the delegate that points to the method UpdateMessage in main UI.
The control used is a TextBox. I have tried other controls like listview,
richtextboxsand, etc. still the same result.
Worker Thread
void DisplayLogs()
{
lock (this)
{
while (logQueue.Count > 0)
{
string logMessagemessage = logQueue.Dequeue();
LogMessage(string.Concat(logMessagemessage, Environment.NewLine));
}
}
}
Main UI
public void UpdateMessage( string message)
{
if (!txtLog.IsHandleCreated)
{
return;
}
if (txtLog.InvokeRequired)
txtLog.BeginInvoke( new UpdateLogDelegate( UpdateLog), message);
else
txtLog.AppendText(message);
}
The main solution is to offload the expensive code onto a background worker and leave your UI thread responsive for UI actions. Your form can then simply show a modal dialog or something.
MSDN - How to use a Background Worker
In this situation it's necessary to move the majority of the work to a new thread, and clear up the UI thread for cancel messages etc.
You are going about this backwards. The main thread should, in theory, always be available to accept user input. Anything that may block for extended periods of time (heavy computation, database access, network access) should be done in a background thread. The idea is to have the edit control's data being computed and populated by a background thread (BackgroundWorker objects work nicely here) so that the main thread is always available if the user clicks on the cancel button.
Your problem is that the your UI thread is ALWAYS busy. I am saying this assuming that the number of items in logQueue is quite large. The while loop doesn't quit till the queue is empty. So it keeps hitting the UI thread with request for updates.
Also the if (txtLog.InvokeRequired) is kind of pointless because you are always calling the method from a worker thread.
So, since a .net WinForm application has only a single UI, which in your case is too busy to process other notifications, the new window appears stuck (because the paint messages are stuck in the message queue and cannot be processed as it is already flooded with the text box update messages)
You could stick an Application.DoEvents inside your loop which will give the message loop some time to process the pending notifications. However this is kind of a hack, in my opinion, as the UI behavior is sometimes not predictable. It may lead to things like stuttering while moving a dialog, delayed responses to click events etc.
Another point, MessageBox.Show or a Form.ShowDialog (if this is what you are using for the cancel button) is a blocking call. The thread on which you show it WILL hang till you dismiss the dialog. Try Form.Show and set the parent property to the main form.
Another alternative is to add a timer and process only X notifications per Y seconds. This will give the UI thread some breathing room for performing other activities.
I am displaying dialog on the main form, now one for loop is working behind but when that dialog will displayed code execution will be stopped, but I don't want to let stop the execution of the code, is any other any way to do that ?
EDIT: I am using now thread to do that and my code is like
Thread t;
private void StartParsingByLoop()
{
t = new Thread(new ThreadStart(RunProgress));
t.Start();
for (int i = 0; i < dialog.FileNames.Length; i++)
{
cNoteToParsed.AllContrctNotesFilePath.Add(dialog.FileNames[i].ToString());
}
cNoteToParsed.ParseContractNote();
if (cNoteToParsed.NOfContrctNoteParsed > 0)
LoadTransactionsInDataGridView();
t.Abort();
}
private void RunProgress()
{
frmProgress progressForImportingTran = new frmProgress("Importing Transactions", "ok");
progressForImportingTran.ShowDialog();
}
Now I have problem is that the dialog that shows the progress does not behave like dialog and gives access of the main form and if we try to access the main form then dialog goes to hide. And I dont want to make the dialog be hide.
You can let a different thread handle the loop.
Response to edit: Can you provide more details, perhaps some code? what is the loop doing? what form do you display?
(this answer is based on the assumption that we are taking about a winforms app)
Show the form using the Show method, rather than ShowDialog. By passing a reference to the main form, the dialog will stay on top of the main form even if it is not modal:
TheDialog dialog = new TheDialog();
dialog.Show(this);
Note though that the user can still interact with the controls on the main form, so you might want to disable some controls, depending on your scenario.
You state in your question that there are requirements that prevent you from using threading for this. This kind of requirement strikes me as odd, and it is a pity because this is one of the typical scenarios when you would want to use some sort of asynchronous construct. By performing heavy work on the UI thread, you get some drawbacks, including:
The UI will not be responsive - if you want to allow the user to cancel the work by clicking a button, that will be tricky to achieve in a robust manner.
The UI will not redraw properly since the UI thread is busy performing other work.
Do not use ShowDialog() but Show() to display the dialog windows. Show() will immediately return to the caller whereas ShowDialog() will hold the execution. Note that when using Show() your dialogs won't be modal anymore.
OK, so you don't need modal dialog, you need a mechanism for your user not to be able to select your main form while processing is enabled.
Modal dialog doesn't mean that execution is stopped - it just happens somewhere else.
There are several methods to do this, and you ruled out new thread creation (don't know why, it would solve it elegantly). If don't use thread, you'll have another problem - your processing should be done in CHUNKS and every little while you'll have to do something like Application.DoEvents() to enable your application to process messages and not be frozen to the user.
So, if you can create a method in the main form that shows your 'please wait' dialog which will perform some work and later do some more until is finished, you can do this:
create a new form (wait dialog)
start a timer inside of it and wire timer to the PARENT form
timer interval should be 1
ShowDialog() the form
on timer event do small amount of work (don't allow it to go more then 1/10 of seconds)
Can you do that?
Anyway:
task can't be split into small workable pieces
you can't use threads
you want your UI to be responsive
PICK 2. You can't have all 3 in Winforms.
Ok, so I found something weird over the weekend. I have a WPF app that spawns off some threads to perform background work. Those background threads then Post work items to my Synchronization Context. This is all working fine except for one case. When my threads finish sometimes they will post an action onto the dispatcher that will open up a Popup window. What ends up happening is that if 2 threads both post an action on the Dispatcher it starts processing one, then if I open up a Popup window with Window.ShowDialog(); the current execution path pauses waiting for feedback from the dialog box as it should. But the problem arises that when the dialog box opens the Dispatcher then goes and starts immediately starts running the second action that was posted. This results in two code paths being executed. The first one with a message box being held open, while the second one is running wild because my application state is unknown because the first action never completed.
I've posted some example code to demonstrate the behavior I'm talking about. What should happen is that if I post 2 actions and the 1st one opens up a dialog box the second action shouldn't run until after the 1st action has been completed.
public partial class Window1 : Window {
private SynchronizationContext syncContext;
public Window1() {
InitializeComponent();
syncContext = SynchronizationContext.Current;
}
private void Button_ClickWithout(object sender, RoutedEventArgs e) {
// Post an action on the thread pool with the syncContext
ThreadPool.QueueUserWorkItem(BackgroundCallback, syncContext);
}
private void BackgroundCallback(object data) {
var currentContext = data as SynchronizationContext;
System.Console.WriteLine("{1}: Thread {0} started", Thread.CurrentThread.ManagedThreadId, currentContext);
// Simulate work being done
Thread.Sleep(3000);
currentContext.Post(UICallback, currentContext);
System.Console.WriteLine("{1}: Thread {0} finished", Thread.CurrentThread.ManagedThreadId, currentContext);
}
private void UICallback(object data) {
System.Console.WriteLine("{1}: UI Callback started on thread {0}", Thread.CurrentThread.ManagedThreadId, data);
var popup = new Popup();
var result = popup.ShowDialog();
System.Console.WriteLine("{1}: UI Callback finished on thread {0}", Thread.CurrentThread.ManagedThreadId, data);
}
}
The XAML is just a Window with a button that calls Button_ClickWithout OnClick. If you push the button twice and wait 3 seconds, you will see you get 2 dialogs popping up one over the other, where the expected behavior would be the first one pops up, then once it's closed the second one will pop up.
So my question is: Is this a bug ? or how do I mitigate this so I can have only one action be processed at a time when the first action halts execution with a Window.ShowDialog() ?
Thanks,
Raul
As I'm awaiting an answer on my question (Advice on using the Dispatcher Priority and Binding) I thought that would pay-it-forward™.
What you are experiencing is Nested Pumping on the Dispatcher. I recommend reading the MSDN article on the WPF Threading Model, especially the section titled 'Technical Details and Stumbling Points' that is two-thirds down the page. The sub-section describing the Nested Pumping is copied below for convenience.
Nested Pumping
Sometimes it is not feasible to completely lock up the UI thread.
Let’s consider the Show method of the MessageBox class. Show doesn’t
return until the user clicks the OK button. It does, however, create a
window that must have a message loop in order to be interactive. While
we are waiting for the user to click OK, the original application
window does not respond to user input. It does, however, continue to
process paint messages. The original window redraws itself when
covered and revealed.
Some thread must be in charge of the message box window. WPF could
create a new thread just for the message box window, but this thread
would be unable to paint the disabled elements in the original window
(remember the earlier discussion of mutual exclusion). Instead, WPF
uses a nested message processing system. The Dispatcher class includes
a special method called PushFrame, which stores an application’s
current execution point then begins a new message loop. When the
nested message loop finishes, execution resumes after the original
PushFrame call.
In this case, PushFrame maintains the program context at the call to
MessageBox.Show, and it starts a new message loop to repaint the
background window and handle input to the message box window. When the
user clicks OK and clears the pop-up window, the nested loop exits and
control resumes after the call to Show.
A modal dialog box does not prevent the owner window from processing messages, otherwise you'd see it fail to redraw as the modal dialog was moved over its surface (just as an example).
In order to achieve what you want, you have to implement your own queue on the UI thread, possibly with some synchronization to "wake it up" when the first work item arrives.
EDIT:
Also if you examine the call stack of the UI thread while the 2nd modal dialog box is up, you might find out that it has the first ShowDialog call above it in the stack.
EDIT #2:
There might be an easier way of doing this, without implementing your own queue. If instead of the SynchronizationContext, you would use the Dispatcher object, you would be able to call BeginInvoke on it with a priority of DispatcherPriority.Normal, and it will get queued properly (check).