Windows forms - wait until form is completely drawn (painted) - c#

I designed a for wich only objective is to run an method.
It's a please wait form.
I give a delegate to it, and call ShowDialog.
It just takes that delegate and executes it in the Shown event. After that, Hide().
The purpose is solely to be there telling user to wait until delegate is run.
BUT: It starts executing the delegate BEFORE it's completely show. I mean, the form appears in the screen, but the please wait label gets stuck by the running code, being shown very late, or even not shown. (An empty rectangle remains in the middle of the form).
I could make form wait maybe two seconds before running the delegate, but is there another way (better and assured) to call the delegate AFTER the form is completely painted, with label shown???
Thanks.

Try using a BackgroundWorker The BackgroundWorker component has 4 events.
You would need to implement 2 of them: DoWork and RunWorkerCompleted
The DoWork method executes in a thread, and is where you do your work ( or in your case, call your delegate)
The RunWorkerCompleted is called when the work is finished, and you can call your Hide() method there.
If you happen to want to show Progress, you can Implement the ProgressChanged event, which allows you to pass in a percentage completed and an object ( normally I pass a message on what I'm doing )
Then just do your Drawing like normal, and call the Background worker RunWorkerAsync to get it called.
public class Wait : Form
{
BackgroundWorker _bgWorker = new BackgroundWorker();
public delegate void toExecuteDele(object args);
public toExecuteDele ToExecute;
public Wait()
{
InitializeComponent();
_bgWorker.DoWork += new DoWorkEventHandler( DoWork )
_bgWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler (WorkDone);
}
public void Execute(object args)
{
// Display Stuff (label, start a progress bar pulsing, maybe enable disable stuff
DispalyStuff(" Plate Wait");
_bgWorker.RunBackgroundAsync(args);
}
private void DoWork(object sender, DoWorkEventArgs e)
{
if( ToExecute != null )
ToExecute(e.Argument);
}
public void WorkDone(object sender, RunWorkerCompletedEventArgs e)
{
// Display an Error if there is an exception in the event args
Hide();
}
}
Everything in the DoWork happens in another thread, Everything else you do runs in the UI thread, and wont hang up while executing.

Related

Why does invoking an event in a timer callback cause following code to be ignored?

I'm writing a simple game that uses timers from the system.threading namespace to simulate wait times for actions. My goal is to have the timer execute once every second for x amount of seconds. To achieve this I added a counter in the timer callback.
The problem is any code I place after invoking the DeliveryProgressChangedEvent event seems to get ignored. My counter is never incremented thus allowing the timer to run forever.
If I invoke the event after I increment the counter, everything works fine. Just nothing after invoking the event will execute. Rather than going the easy route I'd like to understand if not resolve this problem.
I did a fair bit of research into the system.threading timer object as well as events but wasn't able to find any information related to my issue.
I created a bare bones example of my project to demonstrate the issue below.
Game Class
class Game
{
private Timer _deliveryTimer;
private int _counter = 0;
public event EventHandler DeliveryProgressChangedEvent;
public event EventHandler DeliveryCompletedEvent;
public Game()
{
_deliveryTimer = new Timer(MakeDelivery);
}
public void StartDelivery()
{
_deliveryTimer.Change(0, 1000);
}
private void MakeDelivery(object state)
{
if (_counter == 5)
{
_deliveryTimer.Change(0, Timeout.Infinite);
DeliveryCompletedEvent?.Invoke(this, EventArgs.Empty);
}
DeliveryProgressChangedEvent?.Invoke(this, EventArgs.Empty);
++_counter;
}
}
Form Class
public partial class Form1 : Form
{
Game _game = new Game();
public Form1()
{
InitializeComponent();
_game.DeliveryProgressChangedEvent += onDeliveryProgressChanged;
_game.DeliveryCompletedEvent += onDeliveryCompleted;
pbDelivery.Maximum = 5;
}
private void onDeliveryProgressChanged(object sender, EventArgs e)
{
if (InvokeRequired)
pbDelivery.BeginInvoke((MethodInvoker)delegate { pbDelivery.Increment(1); });
MessageBox.Show("Delivery Inprogress");
}
private void onDeliveryCompleted(object sender, EventArgs e)
{
MessageBox.Show("Delivery Completed");
}
private void button1_Click(object sender, EventArgs e)
{
_game.StartDelivery();
}
}
EDIT
Just to clarify what I mean. Any code I put after DeliveryProgressChangedEvent?.Invoke(this, EventArgs.Empty); will not execute. In my example ++_counter will not run. The event does fire and the onDeliveryProgressChanged handler does run.
The problem:
Using a System.Threading.Timer class, when the TimerCallback is called, events are raised, to notify the subscribers of the DeliveryProgressChangedEvent and DeliveryCompletedEvent of custom Game class of the progress of a procedure and the termination of it.
In the sample class, the subscriber (a Form class, here) updates an UI, settings the value of a ProgressBar control and also showing a MessageBox (used in the actual implementation of the class sample shown here).
It appears that after the first event is invoked:
DeliveryProgressChangedEvent?.Invoke(this, EventArgs.Empty);
++_counter;
the line where the _counter should be increased is never reached, thus the code that inspects the _counter to set the Timer to a new value is never executed.
What happens:
The System.Threading.Timer is served by ThreadPool threads (more than one). Its callback is called on a thread other than the UI thread. The events invoked from the callback are also raised in a ThreadPool thread.
The code in the handler delegate, onDeliveryProgressChanged, is then run on the same Thread.
private void onDeliveryProgressChanged(object sender, EventArgs e)
{
if (InvokeRequired)
pbDelivery.BeginInvoke((MethodInvoker)delegate { pbDelivery.Increment(1); });
MessageBox.Show("Delivery Inprogress");
}
When the MessageBox is shown - it's a Modal Window - it blocks the Thread from where it's run, as usual. The code following the line where the event is invoked is never reached, so _counter is never increased:
DeliveryProgressChangedEvent?.Invoke(this, EventArgs.Empty);
++_counter;
The System.Threading.Timer can be served by more than one thread. I'm just quoting the Docs on this point, it's quite straightforward:
The callback method executed by the timer should be reentrant, because
it is called on ThreadPool threads. The callback can be executed
simultaneously on two thread pool threads if the timer interval is
less than the time required to execute the callback, or if all thread
pool threads are in use and the callback is queued multiple times.
What happens, in practice, is that while the Thread where the CallBack is executed, is blocked by the MessageBox, this doesn't stop the Timer from executing the CallBack from another thread: a new MessageBox is shown when the event is invoked and it keeps on running until it has resources.
The MessageBox has no Owner. When a MessageBox is shown without specifying the Owner, its class uses GetActiveWindow() to find an Owner for the MessageBox Window. This function tries to return the handle of the active window attached to the calling thread's message queue. But the thread from which the MessageBox is run has no active Window, as a consequence, the Owner is the Desktop (actually, IntPtr.Zero here).
This can be manually verified by activating (clicking on) the Form where the MessageBox is called: the MessageBox Window will disappear under the Form, since it's not owned by it.
How to solve:
Of course, using another Timer. The System.Windows.Forms.Timer (WinForms) or the DispatcherTimer (WPF) are the natural substitutes. Their events are raised in the UI Thread.
► The code presented here is just a WinForms implementation made to
reproduce a problem, hence these may not apply to all contexts.
Use a System.Timers.Timer: the SynchronizingObject property provides means to marshal the events back to the Thread that created the current class instance (same consideration in relation to the concrete implementation context).
Generate an AsyncOperation using the AsyncOperationManager.CreateOperation() method, then use a SendOrPostCallback delegate to let the AsyncOperation call the SynchronizationContext.Post() method (classic BackGroundWorker style).
BeginInvoke() the MessageBox, attaching it to the UI Thread SynchronizationContext. E.g.,:
this.BeginInvoke(new Action(() => MessageBox.Show(this, "Delivery Completed")));
Now the MessageBox is owned by the Form and it will behave as usual. The ThreadPool thread is free to continue: the Modal Window is synched with the UI Thread.
Avoid using a MessageBox for this kind of notifications, since it's really annoying :) There are many other ways to notify a User of status changes. The MessageBox is probably the less thoughtful.
To make them work as intended, without changing the current implementation, the Game and Form1 classes can be refactored like this:
class Game
{
private System.Threading.Timer deliveryTimer = null;
private int counter;
public event EventHandler DeliveryProgressChangedEvent;
public event EventHandler DeliveryCompletedEvent;
public Game(int eventsCount) { counter = eventsCount; }
public void StartDelivery() {
deliveryTimer = new System.Threading.Timer(MakeDelivery);
deliveryTimer.Change(1000, 1000);
}
public void StopDelivery() {
deliveryTimer?.Dispose();
deliveryTimer = null;
}
private void MakeDelivery(object state) {
if (deliveryTimer is null) return;
DeliveryProgressChangedEvent?.Invoke(this, EventArgs.Empty);
counter -= 1;
if (counter == 0) {
deliveryTimer?.Dispose();
deliveryTimer = null;
DeliveryCompletedEvent?.Invoke(this, EventArgs.Empty);
}
}
}
public partial class Form1 : Form
{
Game game = null;
public Form1() {
InitializeComponent();
pbDelivery.Maximum = 5;
game = new Game(pbDelivery.Maximum);
game.DeliveryProgressChangedEvent += onDeliveryProgressChanged;
game.DeliveryCompletedEvent += onDeliveryCompleted;
}
private void onDeliveryProgressChanged(object sender, EventArgs e)
{
this.BeginInvoke(new MethodInvoker(() => {
pbDelivery.Increment(1);
// This MessageBox is used to test the progression of the events and
// to verify that the Dialog is now modal to the owner Form.
// Of course it's not used in an actual implentation.
MessageBox.Show(this, "Delivery In progress");
}));
}
private void onDeliveryCompleted(object sender, EventArgs e)
{
this.BeginInvoke(new Action(() => MessageBox.Show(this, "Delivery Completed")));
}
private void button1_Click(object sender, EventArgs e)
{
game.StartDelivery();
}
}
Good information. The callback method executed by the timer should be reentrant, because it is called on ThreadPool threads. The callback can be executed simultaneously on two thread pool threads if the timer interval is less than the time required to execute the callback, or if all thread pool threads are in use and the callback is queued multiple times.

How to ask the GUI thread to create objects?

I've got the following program flow in my Windows Forms application (WPF is not a viable option unfortunately):
The GUI Thread creates a splash screen and a pretty empty main window, both inheriting Form.
The splash screen is shown and given to Application.Run().
The splash screen will send an event which triggers an async Event Handler which performs initialization, using the IProgress interface to report progress back to the GUI. (This works flawlessly.)
At some point during the initialization, I need to dynamically create GUI components based on information provided by certain plugins and add them to the Main Window.
At this point I'm stuck: I know I need to ask the GUI thread to create those components for me, but there is no Control I could call InvokeRequired on. Doing MainWindow.InvokeRequired works neither.
The only idea I could come up with was to fire an event which is connected to a factory in the GUI Thread, and then wait for that factory to fire another event which provides the created controls. However I am pretty sure there is a more robust solution. Does anyone know how to achieve this?
Using the comments on my question, especially the note about the continuation method which made me find this very useful question, I achieved the following:
The first part of initialization is performed asynchronously (no change).
The second part of the initialization (which creates the UI elements) is performed afterwards as a Continuation Task, in the context of the UI thread.
Apart from the rather short GUI initialization part, the Splash Screen is responsive (i.e. the mouse cursor does not change to "Waiting" once it hovers the Splash Screen).
Neither of the initialization routines knows the splash screen at all (i.e. I could easily exchange it).
The core controller only knows the SplashScreen interface and does not even know it is a Control.
There currently is no exception handling. This is my next task but doesn't affect this question.
TL;DR: The code looks somewhat like this:
public void Start(ISplashScreen splashScreen, ...)
{
InitializationResult initializationResult = null;
var progress = new Progress<int>((steps) => splashScreen.IncrementProgress(steps));
splashScreen.Started += async (sender, args) => await Task.Factory.StartNew(
// Perform non-GUI initialization - The GUI thread will be responsive in the meantime.
() => Initialize(..., progress, out initializationResult)
).ContinueWith(
// Perform GUI initialization afterwards in the UI context
(task) =>
{
InitializeGUI(initializationResult, progress);
splashScreen.CloseSplash();
},
TaskScheduler.FromCurrentSynchronizationContext()
);
splashScreen.Finished += (sender, args) => RunApplication(initializationResult);
splashScreen.SetProgressRange(0, initializationSteps);
splashScreen.ShowSplash();
Application.Run();
}
It is much easier to manage multiple forms and display one while the other is working or being constructed.
I suggest you try the following:
When application is started you create splash screen form so your Program.cs is like this
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new SplashForm());
}
Inside the splash form constructor, create a new thread (I will use BackgroundWorker but there are other options like tasks) to build your main form.
public SplashForm()
{
InitializeComponent();
backgroundWorker1.WorkerSupportsCancellation = true;
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
backgroundWorker1.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);
backgroundWorker1.RunWorkerAsync();
}
Now we need to write the SplashForm member functions to tell background worker what to do
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
// Perform non-GUI initialization - The GUI thread will be responsive in the meantime
// My time consuming operation is just this loop.
//make sure you use worker.ReportProgress() here
for (int i = 1; (i <= 10); i++)
{
if ((worker.CancellationPending == true))
{
e.Cancel = true;
break;
}
else
{
System.Threading.Thread.Sleep(500);
worker.ReportProgress((i * 10));
}
}
SetVisible(false);
MainForm mainForm = new MainForm();
mainForm.ShowDialog();
//instead of
//this.Visible = false;
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.progressBar1.Value = e.ProgressPercentage;
}
You might have noticed by now, I am using another member function to hide the splash screen. It is because you are now in another thread and you can't just use this.visible = false;. Here is a link on the matter.
delegate void SetTextCallback(bool visible);
private void SetVisible(bool visible)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetVisible);
this.Invoke(d, new object[] { visible });
}
else
{
this.Visible = visible;
}
}
When I run this sample project it shows the progress bar and then loads the MainForm windows form after hiding the SplashForm.
This way you can put any controls that you might need inside the MainForm constructor. The part you shortened as // Perform GUI initialization afterwards in the UI context should go into MainForm constructor.
Hope this helps.

How to use progressbar, backgroundworker, windows form together in C#?

I am stuck on an issue where I am using Backgroundworker to show the progress of my work in a progress bar. Code used for backgroundworker:-
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
Thread.Sleep(200);
for (int i = 0; i <= 100; i++)
{
Delegate del= new DELEGATE(simulateHeavyWork);
this.Invoke(del);
backgroundWorker1.ReportProgress(i);
if (backgroundWorker1.CancellationPending)
{
e.Cancel = true;
backgroundWorker1.ReportProgress(0);
return;
}
}
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
percentLabel.Text = e.ProgressPercentage.ToString() + "%";
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
MessageBox.Show("Cancelled");
}
else
{
MessageBox.Show("Completed");
}
}
I have created a delegate on the code:-
public partial class Form1 : Form
{
private delegate void DELEGATE();
public Form1()
{
InitializeComponent();
}
private void simulateHeavyWork()
{
Thread.Sleep(100);
...lines of code to perform some search logs operation..
....
}
}
The functionality I want to achieve is that progress bar should report the progress of my function simulateHeavyWork() which is actually using UI thread as it needs to take input from my form controls and update it as well.
Now the problem which is happening is that code is actually calling simulateHeavyWork() and gives the output that is updating ui controls and work is done. (Note: I have used delegate here to avoid error cross controls running on ui thread as my function needs to use UI controls.)
Once that is done, it starts updating progress bar and which is wrong and looks like it calls simulateHeavyWork again and again with the gap of sleep(100).
user3222101, as Andy stated before, you are running simulateHeavyWork() continuously. Moreover, by calling Invoke you are running this method in the UI thread which cause an extra sleep in the UI thread. Basically Invoke uses the message loop (pump) of the Control you use it on (Form1 in that case) and put your delegate to the queue of the UI thread in order to execute. This is not a good practice I think, due to the Sleep() call and time consuming log operations in your simulateHeavyWork() method.
I hope, understand you problem clearly.What I suggest is separation of the time consuming log operations from UI thread. Do not spend the valuable time of UI thread with slow and boring I/O operations. Get the values from the controls (using Invoke in the BackgroundWorker as I will explain below), do whatever you want in BackgroundWorker and update your GUI (again using Invoke) without touching the UI thread for this kind of heavy tasks.
As Andy suggested, you can pass data via the parameter of RunWorkerAsync and you should create a class which can store any data you need (because it accepts only one parameter). However, you can get the values from your Form whenever you need from another thread by using Invoke. Invoke
method also returns the value from your delegate (please see the example at the link below) and this gives you a chance to get the values of your controls on the form. Create a delegate which returns an object of type class that you crated for RunWorkerAsync and use this values in the BackgroundWorker thread. Please, have a look at the example in here.
public static string GetTextThreadSafe(this TextBox box)
{
return GetTextBoxText(box);
}
Also, example uses Func<...> in order to return value.
By this way you can sleep (in BackgroundWorker thread) for a while then get the values from your controls (current values) and do whatever you want (again in BackgroundWorker thread). I think, this improves your code.
From your question: "which is wrong and looks like it calls simulateHeavyWork again and again with the gap of sleep(100)."
Of course it calls. Just look at your code:
for (int i = 0; i <= 100; i++)
{
Delegate del= new DELEGATE(simulateHeavyWork);
this.Invoke(del);
So you are calling simulateHeavyWork 100 times here. And since you've typed Thread.Sleep(100); in the body of simulateHeavyWork - gap between calls is about Sleep(100)

Changing a form from a separate background worker

I have a form which when loaded starts a looping background worker which gets data from a usb device every half a second.
Once the program receives a new piece of data from the usb device it runs a function.
The _Dowork function has
while (true)
{
portConnection.Write("?");
Thread.Sleep(50);
}
I then have a separate routine that runs when data is received
private void portConnection_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
}
This is the routine that cannot then set values on the original form as the function is apparently on a separate thread.
How can I make this routine able to influence the original form?
Try something like this:
private void InvokeIfRequired(Control target, Delegate methodToInvoke)
{
if (target.InvokeRequired)
target.Invoke(methodToInvoke);
else
methodToInvoke.DynamicInvoke();
}
Call the method in your ProcessStatsReceived and in the methodToInvoke do your stuff...
You can use it like this in the ProccessStatusReceived:
InvokeIfRequired(this, new MethodInvoker(delegate() { this.lblStatus.Text = (string)e.Data; }));
The report progress part of BackgroundWorker is made for this.
This will make the DoWork method able to call a method on the GUI thread.
See this msdn article for details.
In short, the needed parts are:
Bind the progress changed handler:
bw.ProgressChanged += bw_ProgressChanged;
Set the BW to allow progress reporting:
bw.WorkerReportsProgress = true;
Implement the progress change method:
private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
//your logic to be executed on the GUI thread here
}
Then call it from DoWork like this:
bw.ReportProgress( progressPercentage, userState )
progressPercentage and userState can be user to transfer data from the background thread to the ProgressChanged method on the GUI thread.

C# threading issue

To play a bit with threading, delegates and backgroundworkers, I'm putting together a few small applications, I'm having a bit of trouble with one of them.
I've a Windows form, with a textbox, a button and a richttext.
When I press the button, the text in the textbox is used as a paramter to instantiate a class, like this:
public partial class Form1 : Form
{
private BackgroundWorker backgroundWorker;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
backgroundWorker = new BackgroundWorker();
backgroundWorker.DoWork += new DoWorkEventHandler(worker_DoWork);
backgroundWorker.RunWorkerAsync();
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
new Thread((ThreadStart)delegate()
{
this.BeginInvoke((ThreadStart)delegate()
{
foreach (string line in textBox1.Lines)
{
Dig digger = new Dig(line, textBox1.Text);
digger.DomainChecked += new Dig.DomainCheckedHandler(OnUpdateTicker);
string response = digger.GetAllInfo();
richTextBox1.AppendText(response);
Application.DoEvents();
}
});
}).Start();
}
void OnUpdateTicker(string msg)
{
new Thread((ThreadStart)delegate()
{
this.BeginInvoke((ThreadStart)delegate()
{
label4.Text = msg;
Application.DoEvents();
});
}).Start();
}
}
When debugging I run into a 'textBox1.Lines' threw an exception of type 'Microsoft.VisualStudio.Debugger.Runtime.CrossThreadMessagingException'
Any tips on how to solve this problem?
First, there is no need to create new threads inside DoWork; the whole idea with the BackgroundWorker is that DoWork is executed on a separate thread. Second, since DoWork is executed on a separate thread and UI controls can be modified only on the UI thread, you need to invoke those updates correctly. So, a rewritten version of worker_DoWork could look like this:
void worker_DoWork(object sender, DoWorkEventArgs e)
{
foreach (string line in textBox1.Lines)
{
Dig digger = new Dig(line, textBox1.Text);
digger.DomainChecked += new Dig.DomainCheckedHandler(OnUpdateTicker);
string response = digger.GetAllInfo();
richTextBox1.Invoke((Action) delegate { richTextBox1.AppendText(response); });
}
}
Note how the code does not explicitly spawn any new threads, and also how the AppendText method call is done through a Control.Invoke call, forcing it to execute on the UI thread.
The main reason is that the textbox is not owned by the background thread.
Your UI thread owns all the UI objects, and you're spinning up a background thread when a button is pressed. That background thread should not have access to any UI objects.
If you want the value of the textbox to be used, you'll need to pass it to your background thread another way.
Have a look here for an explanation (and solution).
You can only update controls on the main thread from the main thread itself, unless you explicitly tell your program that it's ok to do, by using the .Invoke method of the control.
From: http://www.albahari.com/threading/part3.aspx
Control.Invoke
In a multi-threaded Windows Forms application, it's illegal to call a method or property on a control from any thread other than the one that created it. All cross-thread calls must be explicitly marshalled to the thread that created the control (usually the main thread), using the Control.Invoke or Control.BeginInvoke method. One cannot rely on automatic marshalling because it takes place too late – only when execution gets well into unmanaged code, by which time plenty of internal .NET code may already have run on the "wrong" thread – code which is not thread-safe.

Categories

Resources