c# Update LED component from everywhere - c#

I have a question related to a simple task: update the control status from any class in the namespace.
In detail I have written a GUI that opens different forms from listbox (each row opens a different form). In some cases the form called has to retrieve data from SQL database, in this kind of scenario I'd like to insert a couple of custom controls (LED) in order to signal to the user when the system is retrieving data (RED LED) or is ready (GREEN LED).
Basically the result is that I'm not able to update these controls during the form loading, I have noticed that a good way can be the backgroundworker but not sure if it is right.
Do you have any idea how to manage it?

Yes, you would need to use a BackgroundWorker or some other asynchronous operation. If you do your logic during FormLoad, which is a synchronous operation, you won't see any updates in your form no matter what.
So, for example with a background worker:
BackgroundWorker bw;
private void Form1_Load(object sender, EventArgs e)
{
bw = new BackgroundWorker();
bw.WorkerReportsProgress = true; //allows call backs
bw.DoWork += bw_DoWork;
bw.ProgressChanged += bw_ProgressChanged;
bw.RunWorkerAsync(); //run the worker
}
void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
var message = e.UserState;
//do something with the UserState on the UI, like flash a light
}
void bw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker localBW = (BackgroundWorker)sender;
//do your logic here, when you want to "report progress"
//use the following lines
localBW.ReportProgress(0, "hey, flash please"); // or something else
//when you are all done, you can pass something back
e.Result = "I'm done now";
}
For more reading, see the msdn page on BackgroundWorker, a "How to:" using a BackgroundWorker, and Asynchronous Programming with Async and Await (a newer topic with .net 4.0 and up).

Related

Blocking User Process Controller from executing before Control on Winform Fully Loaded and Shown

I'm creating a Excel VSTO using c#. My operation is easy, just right-click on the cell and click on "Update" and a winform that shows progress status will prompt out and launch the controls on the form is tied to a User Process Controller.
The problem is now that the process has launched and executed before the form is fully load, is there a way that I can block the user process controller from executing before all the control on the progress status form is fully shown and loaded? The image below depict my condition.
I have tried to put my User Process Controller call in Form Activated, Shown, Loaded, and nothing works.
This is the first stage the form loaded. Note : The two line of text has shown that the user control process has been executed.
This is the second stage the form loaded.
This is third stage
And finally it is fully loaded.
I have discovered a "hack" to overcome this issue. I add in a backgroundworker and done the follow code on the Form Constructor.
public SummaryStatus()
{
InitializeComponent();
backgroundWorker = new BackgroundWorker();
backgroundWorker.DoWork += backgroundWorker_DoWork;
backgroundWorker.RunWorkerCompleted += backgroundWorker_RunWorkerCompleted;
backgroundWorker.RunWorkerAsync();
}
And on the DoWork event of BackgroundWorker
void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
System.Threading.Thread.Sleep(2000);
}
and finally, I added the following code in Run Worker Completed
void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
UpdateUPC upc = new UpdateUPC();
upc.txtUpdateSummary = txtUpdateProgress;
upc.updateProgressBar = UpdateProgressBar;
upc.UpdateStatusOnItem();
}
I understand the above might not be a acceptable solution but it might just provide a workaround for the issue. However, if anyone who has a better solution, do feel free to drop in your suggestion.

Making a text appear under a button for half a second

I want to make a button to perform some action and THEN let user know it was done. I tried making a label ander a button, then pause, then making it disappear.
private void button1_Click(object sender, EventArgs e)
{
// some action
label1.Text = "Done!";
System.Threading.Thread.Sleep(500);
label1.Text = "";
}
But it doesn't work. What is my mistake?
As Grant says in his answer, you're blocking the UI thread. The simplest solution is to spawn a new Task which will do the update for you, thus releasing the UI thread.
The Task can simply use Invoke to push the update back to the UI thread after a Sleep.
In your case, this translates to something like this:
private void button1_Click(object sender, EventArgs e)
{
// some action
label1.Text = "Done!";
new TaskFactory().StartNew(() =>
{
Thread.Sleep(5000);
Invoke((Action)(() => label1.Text = string.Empty));
});
}
The call to Thread.Sleep() freezes the UI thread for a half-second, so that no updates to the UI can happen (including your update to the Label's text).
Here's a couple options:
You could use a BackgroundWorker, which has built-in mechanisms for executing long-running code in a separate thread, and then updating the UI (such as your Label) when it's done.
You could add a Windows.Forms.Timer to your Form, to perform the action and update the Label. Give it an interval of 500 (ms), and it'll wait roughly a half-second before firing.

Loading message on form

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.

Progressbar with a timer

I have a piece of code which gets the available server instances from my SQL server to DataTable. It will take some time to populate. So I tried to add a progress bar to indicate that time. There are some ways to do that:
1.Using Background worker
2.Using a timer
I used a timer but progress bar is not animating.
my code is(WinForm) :
private void frmCodeGenerator_Load(object sender, EventArgs e)
{
Cursor.Current = Cursors.WaitCursor;
tmrWaiting.Enabled = true;
tmrWaiting.Start();
//List all available servers
PopulateServerInstances();
//List all default namespaces to import
//Set the target folder default path tec.
tmrWaiting.Stop();
Cursor.Current = Cursors.Default;
}
private void tmrWaiting_Tick(object sender, EventArgs e)
{
pbWaiting.Value++;
}
Cursor is changing form normal to waitstate but progress bar didn't. Am I doing wright?
Your Load event handler, which is running on your UI thread, is calling PopulateServerInstances, which is presumably blocking whilst loading the data. You need to do that work on a BG thread so that your UI thread can continue to update the UI. BackgroundWorker is your best bet, because it includes support for updating progress information on the UI thread as you go along.

Popping a MessageBox for the main app with Backgroundworker in WPF

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));

Categories