I've put a loading message like this on my form :
public void myFunc()
{
lbl_status.Text = "Loading ... Please Wait";
// Some Database Works
lbl_status.Text = "Done";
}
but there is a problem. Some times when I click on the button ( Which does myFunc method ) my application doesn't show the Loading message. It just does the database work than it will show Done message.
I know that sometimes the database work is very fast so Loading message won't show but sometimes it is not that fast, like the fist time I open my app. At that time my application seems to be disabled and no buttons and no textBoxes and ... works and after the database work it will be OK and show Done message and never shows Loading message again!
You can do your db work in background thread:
public void myFunc()
{
lbl_status.Text = "Loading ... Please Wait";
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += bw_DoWork;
bw.RunWorkerCompleted += bw_RunWorkerCompleted;
bw.RunWorkerAsync();
}
EDIT: oops DB works should be in the DoWork event handler :)
void bw_DoWork(object sender, DoWorkEventArgs e)
{
// Some Database Works
}
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
lbl_status.Text = "Done";
}
Your application, like all Windows GUI application, needs to process the GUI events. It is these events that do things like refresh the labels and make your application seem 'responsive'. See Window Messages. Your winforms application runs the message loop when you call Application.Run:
Begins running a standard application message loop on the current thread
If you block the processing while waiting for database work then it will stop refreshing and it will be unresponsive (not respond to clicks or keyboard). So you have to do your database w/o blocking the main loop. There are several options:
use a BackgroundWorker.
use ThreadPool.QueueUserWorkItem.
use the async database methods like SqlCommand.BeginExecuteReader and completion callbacks
use await methods like SqlCommand.ExecuteReaderAsync
Each method has pros and cons, the easiest to start with is probably the first one. Be aware that from a background thread, or from a completion callback, you must use the Control.Invoke when interacting with the main GUI (ie. when updating the form or any element on it).
Any updates to UI objects must be done on the UI thread.
You should look into Control.Invoke to provide you a way to put your call on the Display thread the Control is on.
http://msdn.microsoft.com/en-us/library/system.windows.forms.control.invoke%28v=vs.110%29.aspx
for further clarification, this also means that your heavy updates should be done on a background thread.
WorkerThread may be a good solution for you.
Related
I have a WPF application that calls a dll when user clicks on a button. This dll does very lengthy operations and while it does that, I cannot interact with the MainWindow, e.g scroll down the Datagrid logging some update messages, switch tabs etc.
How can I keep the MainWindow activated while the dll is running ? I thought about a BackgroundWorker that constantly calls the .Activate() method whenever Window.Deactivated occurs, but wouldn't that be terribly resource-consuming and slow down the other dll that already takes a lot of time ?
I'm waiting for suggestions :)
Thank you
It seems like you are running that lengthy operation in the UI thread. Try running it in a separate thread.
private void Button_Click(object sender, RoutedEventArgs e)
{
Task.Factory.StartNew(CallLengthyDllMethod);
}
private void CallLengthyDllMethod()
{
Thread.Sleep(10000); // simulating lengthy operations
MessageBox.Show("Done!");
}
When my main form loads I want to do some processing like loading the last client viewed. This extra processing causing the drawing of the form to be pretty ugly. I am doing it on form shown so I would expect that the form would be done painting.
I have used PostMessage... Is there a modern day version of PostMessage? During this processing I would like to set the hourglass cursor.
I think what you are trying to do is background processing. So you can use a BackgroundWorker class. When your form loads, you would start the background worker and when the worker is finished, it will notify your form through the RunWorkerCompleted event. You can even get progress updates from the worker by implementing ProgressChanged event.
How about using Task + ContinueWith. (If you upgrade to .Net 4.5 async/await would be a better choice).
private void Form_Load(object sender, EventArgs e)
{
Task.Factory.StartNew(() =>
{
Thread.Sleep(5000); //Some long Running Jobs
return "Finished";
})
.ContinueWith(t =>
{
this.Text = t.Result; //Update GUI
},
TaskScheduler.FromCurrentSynchronizationContext());
}
I am not sure why would you want to use PostMessage. If you post a message using PostMessage it will eventually be obtained by GetMessage and unless the message is posted from a different thread whole exercise seems a little futile. Am I missing something here? As for BackgroundWorker please refer to the solution for Label is not set until method is done.
I'm writing a forms application. I'm adding a piece that allows you to double click on a row of a datagridview control to open a new form with more details. These additional details are pulled from a database, which takes a bit of time to finish.
If I run the DB query from the form's load event, the form doesn't show up until everything in the load event has completed, which can take several seconds.
I want the form to show up instantly when you double click, and all of the fields to be populated once the data is ready.
Is there an event I should be using other than Load?
The standard way to accomplish this is to use a background worker thread and disable the button until the worker thread completes. There is a complete event you can subscribe to on the background worker.
You should use threading. Kick off a thread to do the data retrieval in the form's load event. Introduction to threading
You should use a BackgroundWorker to load the data in a background thread without freezing the UI.
If you really want to load on the UI thread, you should handle the Shown event.
This is an c# example using BackgroundWorker as the other posts metioned that loads unit definitions from .xml an file and changes the status label when it finishes. I stuck in the form intializer, but maybe it is better to start it in an OnLoad() override.
public MainForm()
{
InitializeComponent();
BackgroundWorker bw = new BackgroundWorker();
bw.WorkerReportsProgress = false;
bw.WorkerSupportsCancellation = false;
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
unitsToolStripLabel.Text = "Loading Units";
bw.RunWorkerAsync();
}
void bw_DoWork(object sender, DoWorkEventArgs e)
{
...
}
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
unitsToolStripLabel.Text = string.Format("{0} Units Loaded", Units.UnitLibrary.WorkingSet.Count);
unitsToolStripLabel.LinkBehavior = LinkBehavior.HoverUnderline;
unitsToolStripLabel.Click += new EventHandler(unitsToolStripLabel_Click);
}
Please explain a little more on why you do not want to use threading/backgroundworker?
Whilst the correct way to do this is the BackgroundWorker thread, a quick and dirty method is to start a timer on the Load event and get the data when the timer expires. Say 10ms is enough for the form to be painted, then you can disable the controls and set the cursor busy while you get the data. But this is still going to lock up the UI thread while the database is busy leading to repainting artifacts eg if part of the window is covered, and doesn't allow you to display progress using a progress bar.
You can let the load event finish, then start another method to pull data from your database. The initialization of the UI can be done after the form has completed loading, but make sure your UI controls are disabled while you're initializing them.
I have the following code, Some times ArchiveFiles() take more time to complete its execution.
When user clicks exit option more than one time from context menu, Application becomes non responding if ArchiveFiles() takes more time. How can I show a wait message when he clicks the exit option again?
private void exitToolStripMenuItem_Click(object sender, EventArgs e)
{
ArchiveFiles();
Application.Exit();
}
Thanks
Bhaskar
You can use BackgroundWorker.
Using BackgroundWorker, you will be able to execute time consuming tasks such as the one you have on another thread so that the UI doesn't freeze. You will be able to report the progress of that task, then report it accomplishement and executing what ever logic you need after its completion.
Handle the BackgroundWorker.DoWork event to start the operation that performs the potentially time-consuming work.
Handle BackgroundWorker.ProgressChanged event to report the progress of an asynchronous operation to the user.
Finaly, handle BackgroundWorker.RunWorkerCompleted event to implement whatever logic you want to be implemented after the task has been completed.
Refer to the following:
BackgroundWorker Component Overview
C# BackgroundWorker Tutorial
Create a new WaitingForm and put an image control on the form and use the below .gif in that image control which which automatically animate. Then use the code below:
private void exitToolStripMenuItem_Click(object sender, EventArgs e)
{
using (var wait = new WaitingForm())
{
wait.Show();
ArchiveFiles();
Application.Exit();
}
}
This may not be the best solution but it is the quickest. If you want the form to be a dialog, use wait.ShowDialog(); instead and carry ArchiveFiles(); Application.Exit();
functions inside it (if that is a probability).
You'd have to run ArchiveFiles asynchronously, by either using a thread or task, or making some delegate and using its BeginInvoke method.
You should use then some synchronization object like ManualResetEvent so the main thread don't continue executing, calling Application.Exit(). Or you could use some event to know when the operation finishes and then call Application.Exit() (or call it inside ArchiveFiles), but I find that to be worse.
In a WPF app, I am using a BackgroundWorker to periodically check for a condition on the server. While that works fine, I want to pop a MessageBox notifing the users if something fails during the check.
Here's what I have:
public static void StartWorker()
{
worker = new BackgroundWorker();
worker.DoWork += DoSomeWork;
worker.RunWorkerAsync();
}
private static void DoSomeWork(object sender, DoWorkEventArgs e)
{
while (!worker.CancellationPending)
{
Thread.Sleep(5000);
var isOkay = CheckCondition();
if(!isOkay)
MessageBox.Show("I should block the main window");
}
}
But this MessageBox does not block the main window. I can still click on my WPF app and change anything I like with the MessageBox around.
How do I solve this? Thanks,
EDIT:
For reference, this is what I ended up doing:
public static void StartWorker()
{
worker = new BackgroundWorker();
worker.DoWork += DoSomeWork;
worker.ProgressChanged += ShowWarning;
worker.RunWorkerAsync();
}
private static void DoSomeWork(object sender, DoWorkEventArgs e)
{
while (!worker.CancellationPending)
{
Thread.Sleep(5000);
var isOkay = CheckCondition();
if(!isOkay)
worker.ReportProgress(1);
}
}
private static void ShowWarning(object sender, ProgressChangedEventArgs e)
{
MessageBox.Show("I block the main window");
}
Replace
MessageBox.Show("I should block the main window");
with
this.Invoke((Func<DialogResult>)(() => MessageBox.Show("I should block the main window")));
that will cause the message box to be on the main thread and will block all access to the UI until it gets a response. As a added bonus this.Invoke will return a object that can be cast in to the DialogResult.
It doesn't only not block the main window, it is also very likely to disappear behind it. That's a direct consequence of it running on a different thread. When you don't specify an owner for the message box then it goes looking for one with the GetActiveWindow() API function. It only considers windows that use the same message queue. Which is a thread-specific property. Losing a message box is quite hard to deal with of course.
Similarly, MessageBox only disables windows that belong to the same message queue. And thus won't block windows created by your main thread.
Fix your problem by letting the UI thread display the message box. Use Dispatcher.Invoke or leverage either the ReportProgress or RunWorkerCompleted events. Doesn't sound like the events are appropriate here.
Call ReportProgress and pass this to MessageBox.Show.
As Stephen and Hans have said, use the ReportProgress event, and pass data to the UI thread. This is especially important if you want to do anything other tha a MessageBox (for isntance, update a control) because the background thread can't do this directly. You'll get a cross-thread exception.
So whatever you need to do (update progress bar, log messages to the UI, etc.), pass data to the UI thread, tell the UI thread what needs to be done, but let the UI thread do it.
I modified it like this and worked fine for me
return Application.Current.Dispatcher.Invoke(() => MessageBox.Show(messageBoxText, caption, button, icon));