I have a click event which causes the phone to vibrate once a button is clicked. This generally works great, except sometimes the vibration doesnt stop until I completely close the application. I would like to give the application time to complete its vibration and then continue with the rest of the method, but I do not want to block the UI at all. How might I accomplish this
MainPage.xaml.cs
void newButton_Click(object sender, EventArgs e)
{
//Button vibration
if (Settings.EnableVibration.Value) //boolean flag to tell whether to vibrate or not
{
VibrateController.Default.Start(TimeSpan.FromMilliseconds(100));
//place vibration stop here?
}
this.NavigationService.Navigate(new uri("/NewPage.xaml", UriKind.Relate));
}
I have already tried VibrationController.Default.Stop(); but this completely eliminates the vibration all together. Is there a way to simply wait until after the vibration has completed to then navigate to a new page, or do whatever other action the method should perform? Any reccomendations or advice on this implementation or other suggestions?
You can use asynchrony to prevent blocking the UI. Rather than actually blocking the UI thread you need to schedule an action to happen again 100ms from now. Adding a continutation to a Task.Delay call can do just that:
void newButton_Click(object sender, EventArgs e)
{
Action navigate = () =>
this.NavigationService.Navigate(new uri("/NewPage.xaml", UriKind.Relate));
if (Settings.EnableVibration.Value) //boolean flag to tell whether to vibrate or not
{
VibrateController.Default.Start();
Task.Delay(100).ContinueWith(t =>
{
VibrationController.Default.Stop();
navigate();
});
}
else
navigate();
}
.NET wraps this functionality up conveniently in the BackgroundWorker class.
private void SomeMethod()
{
// Create backgroundworker
BackgroundWorker bw = new BackgroundWorker();
// Attach event handler
bw.DoWork += bw_DoWork;
// Run Worker
bw.RunWorkerAsync();
}
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
// Do background stuff here
}
It also has support for progress updates and triggers an event on completion, as far as I know, this functionality extends to windows phone. All of this is covered in the MSDN article.
I would guess what you want to do is call vibrate in the BackgroundWorker, and you can listen for the RunWorkerCompletedEvent which will fire when it is finished. Also, you can happily pause this "thread" and it will not interfere with the UI.
Related
In some games, there is a splash screen that downloads content from a server. It might give some tips while you're waiting. I'm doing something similar, only that the loading happens really quick, but I want it to wait a few more seconds.
When the user first loads my application, it has a screen with a progress bar. At the moment, it checks if the server is online. If it is, it says "Connected!" However, it immediately fades out my controls. I want it to wait about 5 more seconds so the reader can read it. Then fade the controls out.
private void frmMain_Loaded(object sender, RoutedEventArgs e)
{
// Start background worker
m_bgWorker = new System.ComponentModel.BackgroundWorker();
m_bgWorker.ProgressChanged += M_bgWorker_ProgressChanged;
m_bgWorker.WorkerReportsProgress = true;
m_bgWorker.RunWorkerAsync();
m_bgWorker.ReportProgress(100);
}
private void M_bgWorker_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
{
progConnect.Value = 0;
if (SystemBO.IsOnline())
{
lblConnection.Content = "Connected!";
}
progConnect.Value = e.ProgressPercentage;
// TODO: Wait 5 seconds here...
// Fade out controls
lblTitle.BeginAnimation(Label.OpacityProperty, doubleAnimation);
progConnect.BeginAnimation(Label.OpacityProperty, doubleAnimation);
lblConnection.BeginAnimation(Label.OpacityProperty, doubleAnimation);
}
Note:
I tried System.Sleep(), but that made no difference. I understand why. The idea is the same though: I want the background worker to sleep for 5 seconds before completing.
Solution:
I added a few more events: DoWork and RunWorkerCompleted.
Then I added this code:
private void M_bgWorker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
for (int i = 0; i < 100; i++)
{
m_index++;
System.Threading.Thread.Sleep(40);
m_bgWorker.ReportProgress(m_index);
}
System.Threading.Thread.Sleep(3000); // wait 3 seconds to read
}
It worked. The progress bar animates quite smoothly.
You shouldn't do the fade-out in the ProgressChanged handler. That handler really should only be used to update progress information like text and a percentage bar.
Your fade-out logic should be moved to the separate RunWorkedCompleted event handler. Here you can let the thread sleep/await a scheduled task for a while before beginning to fade out the controls. (Using async/await over Thread.Sleep() here has the advantage of not blocking the UI thread while waiting, so the UI still remains responsive while waiting for the time to pass.)
It also ensures that the code is called at the very end and not every time the worker reports any kind of progress. And it's also cleaner because it allows you to handle different termination states by checking the RunWorkedCompletedEventArgs:
If e.Error is set, an exception occurred in the worker thread.
If e.Cancelled is true, the worker thread got cancelled by calling CancelAsync() on the worker. (Only possible if WorkerSupportsCancellation is set to true and only useful if code in the DoWork handler actually checks for the cancellation flag)
Otherwise everything went okay.
Side note:
I am assuming that your code is not a full example, because you have no actual handler assigned to the DoWork event. So right now it would not do anything. And calling ReportProgress() right from the main thread is wrong as well. That method is designed to be called from within the DoWork event handling method in order to let the asynchronous thread report status updates back to the UI, since those events are handled on the main thread.
rather than the last line in frmMain_Loaded:
m_bgWorker.ReportProgress(100);
I would put a for loop with Sleep(), for simulating some extra processing:
for (int i=0; i<=100; i+=10)
{
if (SystemBO.IsOnline())
i = 100;
Sleep(1000);
m_bgWorker.ReportProgress(i);
}
I have a Console app that displays a WinForms Form.
In the Form, the user clicks button 1 and it runs a long subroutine. I want to have a button 2 that can kill the subroutine at any point. However, the UI freezes when I click button 1 until the subroutine has finished. How can I get the UI to not freeze?
Your long-running code is blocking the UI thread, so you can no longer click the second button, nor interact with the UI in any way until the code is finished executing.
You'll need to move your long-running code to a separate thread. There are various (and newer) ways of doing this, but one way is the BackgroundWorker. It's pretty easy to learn, and wraps some nice functionality, like cancelling the thread.
Here's a short WinForms app to demonstrate. You have to explicitly enable the ability for the thread to be cancelled. In this example, the while loop continues indefinitely, but checks every 100ms to see if there's a request for it to be cancelled. When you click the second button, the cancellation request is sent, and the thread ends.
public partial class Form1 : Form
{
private BackgroundWorker bg;
public Form1()
{
InitializeComponent();
bg = new BackgroundWorker
{
WorkerSupportsCancellation = true
};
bg.DoWork += (sender, args) =>
{
while (true)
{
Thread.Sleep(100);
if (bg.CancellationPending)
break;
}
MessageBox.Show("Done!");
};
}
private void button1_Click(object sender, EventArgs e)
{
bg.RunWorkerAsync();
}
private void button2_Click(object sender, EventArgs e)
{
bg.CancelAsync();
}
}
Following up on chouaib's comment, another nice thing about using the BackgroundWorker in a WinForms environment is that you can drop and drop it onto your designer, similar to a Menu, Timer, etc. You can then access its members in the "properties" panel, setting "WorkerSupportsCancellation" to true, subscribing to events, etc.
From your comment:
"is there a way to run this background process and be able to update the main userform? I keep getting the "cross-thread operation not valid control accessed from a thread other than the..." I want to run the long-running background operation, and have it update the main UI with text in a label (like a percentage of its progress)"
If you want to update the UI while the thread is running, you should do that from the ProgressChanged event. First, enable that option and subscribe to the event:
bg.WorkerReportsProgress = true;
bg.ProgressChanged += bg_ProgressChanged;
Then call ReportProgress() when you want to update the UI. You could pass back a percentage complete and some text, for instance:
bg.ReportProgress(50, "Almost there...");
Finally, update the UI from inside the event:
void bg_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
var message = e.UserState.ToString();
var percent = e.ProgressPercentage;
lblStatus.Text = message + " " + percent;
}
You need to make it multithreaded as suggested in the comments. The older way of doing this was manage your own thread. Then along came the background worker (cheap and easy). Now a days you have other options such as the Task Library.
Remember - anything the runs on the UI thread prevents the UI from sending and receiving events until that operation is finished.
Look into the BackgroundWorker component
I am working on application that uses BackgroundWorker Thread. I have a button click event on which I'm doing following things
btnLocate_Click(Object sender, EventArgs e)
{
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += bw_DoWork;
bw.RunWorkerCompleted += bw_RunWorkerCompleted;
bw.RunWorkerAsync(lstNumbers.CheckedItems[0].ToString());
}
In the Background Worker do work event I'm adding some values in globally defined ObservableCollection like this
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
try
{
lock (locker)
{
_RecData.Add(new RecNumberData
{
// Some Values
});
}
}
In BackgroundWorker complete event I'm setting this collection as data source for grid and start a timer that create a new BackgroundWorker and do the same job again.
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
grid.DataSource = RecData;
timer1.Start();
}
private void timer1_Tick(object sender, EventArgs e)
{
timer1.Stop();
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += bw_DoWork;
bw.RunWorkerCompleted += bw_RunWorkerCompleted;
bw.RunWorkerAsync(lstNumbers.CheckedItems[0].ToString());
}
Now the code runs fine when first time BackgroundWorker runs. But when second time it runs after Timer ticking event, Exception raised at line Cross Thread Opeartion Detected.
_RecData.Add(new RecNumberData
{
// Some Values
});
What could be the cause?
Speculation without seeing more code, but I would suspect that:
When you are adding values to the collection the first time through, the collection isn't bound to the UI => no problem
The second time through it is bound to the UI (in the first complete event), so your background worker is attempting to update the UI.
The solution might be to create a second observable collection for your second BackgroundWorker. Or defer adding the results to the collection until the Completed event.
You are modifying a GUI object from outside the GUI thread. You shouldn't do that.
By setting RecData as the data source of a GUI object, every change to RecData will trigger a notification that will change the GUI object. After binding RecData you are changing RecData, and therefore your GUI object, from the background worker thread. Objects that are databound to a GUI object must not be modified by any thread other than the GUI thread.
You can use Control.BeginInvoke to enqueue an action on the GUI thread.
You're adding data to a variable that is used as a datasource to a graphical component (grid), you need to tell us what UI technology you're using (WPF? WInform?).
What's happening is you're doing something on a thread that isn't the one the UI runs on, and most UI technologies do not allow things that affect them running on another thread, there are mechanisms to work around this but they depend on the tech.
Most of the time you'll want to register the code that changes data the UI reads to run on a dispatcher (it will be queued and run on the main thread as soon as time is available)
I would like to change the backgroundcolor of a button for a couple of seconds to lime and then change it back to normal. For some reason it simply doesnt work, I have to wait for those seconds and some things I tested also work but the backgroundcolor isn't changed. This is what I've tried so far:
private void button1_Click(object sender, EventArgs e)
{
button1.BackColor = Color.Lime;
Thread.Sleep(2000);
button1.BackColor = SystemColors.Control;
}
Hopefully someone can help me out with this!
As Horaciux mentioned, you can make use of a Timer object to do this.
Alternatively, you can make use of await and Task.Delay, as mentioned by Jon Skeet in this answer.
private async void button1_Click(object sender, EventArgs e)
{
button1.BackColor = Color.Lime;
await Task.Delay(2000);
button1.BackColor = SystemColors.Control;
}
The problem with Thread.Sleep is that it is a blocking operation - in that it will stop anything happening on the thread in question. There are other issues too though - chiefly that Thread.Sleep doesn't guarantee reactivating the thread in the time specified. When you execute Thread.Sleep, you are basically telling the CPU that your thread doesn't need to be active for the time specified - however, the CPU only guarantees that it won't process it for that time - it doesn't guarantee that it will reactivate at that time, merely at some point after the sleep period (once it has finished processing any other threads currently active).
Since you are adding Thread.Sleep to your UI thread, you are effectively locking up the entire application for the duration of the sleep. await Task.Delay on the other hand, won't block the thread, but will return to that line of code in the function once the task in the await has completed (in this case, a Delay action). This allows all other methods to continue to operate as normal (button clicks will work fine, etc). This method has the added advantage of keeping all the relevant code in one place, rather than having part of it in a different method.
You need a timer.
Add a timer control from the toolbox to your form. Double-click on it to add a timer tick event handler
private void button1_Click(object sender, EventArgs e)
{
button1.BackColor = Color.Lime;
//Set time between ticks in miliseconds.
timer1.Tick=2000;
//Start timer, your program continues execution normaly
timer1.Start;
//If you use sleep(2000) your program stop working for two seconds.
}
//this event will rise every time set in Tick (from start to stop)
private void timer1_Tick(object sender, EventArgs e)
{
//When execution reach here, it means 2 seconds have passed. Stop timer and change color
timer1.Stop;
button1.BackColor = SystemColors.Control;
}
Or you could use a Storyboard if you're working with a XAML technology. I ain't posting implementation details, since they may vary depending on the target technology.
Below is my coding:
Form2 msgForm;
private void button3_Click_1(object sender, EventArgs e)
{
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
msgForm = new Form2();
try
{
bw.RunWorkerAsync();
msgForm.ShowDialog();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
void bw_DoWork(object sender, DoWorkEventArgs e)
{
// Coding that transmit protocol and will last around 2 minutes.
}
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
msgForm.Close();
}
I use the background worker method everytime I click a button to transmit protocols that last around 2 minutes. During transmission, the From2 will show 'Please wait'.
But I have some problem using this coding. The problem is like, when I click the button the first time, it will transmit the protocol once. After that I click again which is the second time, it transmit the protocol twice. After that I click again which is the third time, it transmit the protocol 3 times.... And so on. The times of protocol of transmit increase each time I click the button.
Aren't that it will only run once the coding in void bw_DoWork everytime I click the button?
Is there something wrong with my coding?
You're appending an additional handler every time you click, and then it's run along with everything you added before, which stays where it is (because the object is still there, you're re-using it).
To solve this, you need to either:
Move the declaration of the background worker inside the method (so it's new every time, with only one DoWork handler
Like this:
private void button3_Click_1(object sender, EventArgs e)
{
BackgroundWorker bw = new BackgroundWorker();
// rest of your code
}
Move the .DoWork += ... which appends the handler in the constructor of the class
It mostly depends on if you use that worker somewhere else.
It looks like a new worker starts on every click.
To avoid this behaviour, check if the worker is busy before starting it again.
try
{
if (!bw.IsBusy)
bw.RunWorkerAsync();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
And you can disable the button while the backgroundworker does its job, by button3.Enabled = false; and re-enable it in the bw_RunWorkerCompleted method, letting the user understand he has to wait and cannot click again until the process is finished.
First make sure that your lists/Collections are clear before transmit codes.
Then Use BreakPoint in your source code and remember that your BackgroundWorker can't run twice or more because you use ShowDialog.
Check if the worker is busy:
if (!bw.IsBusy)
bw.RunWorkerAsync();
I would also disable the button and change the text to for example 'Running' while the process is being excecuted.
Use the bw_RunWorkerCompleted method event call to then re-enable the button and change the text. This method runs on the same thread as the UI so there are no cross thread issues.
On your design, why are you showing another form for the 'Please wait' notification? I would suggest updating a label on your existing form either before the async process starts (so not cross thread UI issues) or if you need to update after then you can use the following:
lblNotify.Invoke(new Action(() => lblNotify.Text = #"Please wait"));
The above will then allow you to run your request on the main thread.