This is the first post and it was inspired by the question on SO. To better describe the problem, let’s make a very simple App which will have three TextBoxes:
1 2 3 |
<TextBox x:Name="FirstBox" InputScope="Number" Grid.Row="0"/> <TextBox x:Name="SecondBox" InputScope="Number" Grid.Row="1"/> <TextBox x:Name="ThirdBox" InputScope="Number" Grid.Row="2"/> |
The main task of our App is that it should update all three TextBoxes, depending on the user input. So in the code behind we will create an event handler and subscribe it to TextBox.TextChanged event (in this simple example I’ll just put the text informing which TextBox was changed):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public MainPage() { InitializeComponent(); FirstBox.TextChanged += Box_ChangedOne; SecondBox.TextChanged += Box_ChangedOne; ThirdBox.TextChanged += Box_ChangedOne; } private string info = ""; private void Box_ChangedOne(object sender, TextChangedEventArgs e) { TextBox modifedTextBox = sender as TextBox; if (modifedTextBox.Name == "FirstBox") info = "User modified FirstBox"; else if (modifedTextBox.Name == "SecondBox") info = "User modified SecondBox"; else if (modifedTextBox.Name == "ThirdBox") info = "User modified ThirdBox"; FirstBox.Text = info; // this line fires next Box_ChangedOne event SecondBox.Text = info; // this line fires next Box_ChangedOne event ThirdBox.Text = info; // this line fires next Box_ChangedOne event } |
As you can see, I’ve subscribed Box_ChangedOne to all three TextBoxes – let’s update everything right after one of them changes (why not?). Here comes the first problem, just after we focus on any control and try to put text – our App hangs. Aha – longer look at the code and it turns out that we are in an infinite loop – our event fires another one (cause it is changing TextBox.Text) and so on. So we will improve the event handler with a flag which will inform if the change was invoked by user or from the code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
private bool dontChangeFlag = false; private void Box_ChangedTwo(object sender, TextChangedEventArgs e) { if (!dontChangeFlag) { TextBox modifedTextBox = sender as TextBox; if (modifedTextBox.Name == "FirstBox") info = "User modified FirstBox"; else if (modifedTextBox.Name == "SecondBox") info = "User modified SecondBox"; else if (modifedTextBox.Name == "ThirdBox") info = "User modified ThirdBox"; dontChangeFlag = true; // set the flag to prevent next fired event from further changing Boxes FirstBox.Text = info; SecondBox.Text = info; ThirdBox.Text = info; dontChangeFlag = false; // enable changing } } |
Looks great, so we should test it. Oops, the program hangs – what is wrong? The solution comes with the MSDN: The TextChanged event is asynchronous. Our simple code is racy, and line dontChangeFlag = false; is fired before the events – so we are again in an infinite loop. So why not delay a little our process and wait for other events to finish?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
private async void Box_ChangedThree(object sender, TextChangedEventArgs e) { if (!dontChangeFlag) { TextBox modifedTextBox = sender as TextBox; if (modifedTextBox.Name == "FirstBox") info = "User modified FirstBox"; else if (modifedTextBox.Name == "SecondBox") info = "User modified SecondBox"; else if (modifedTextBox.Name == "ThirdBox") info = "User modified ThirdBox"; dontChangeFlag = true; // set the flag to prevent next fired event from further changing Boxes FirstBox.Text = info; SecondBox.Text = info; ThirdBox.Text = info; await Task.Delay(1000); // let's give some time to change Boxes dontChangeFlag = false; // enable changing } } |
This time we get what we wanted – our flag is working. So far so good – but how to predict how long will it take the phone to finish the events? The answer is – we can’t and don’t do it – let the event informs when it is finished. For this purpose I’ll use a SemaphoreSlim class. And here we will also improve our code (to show the next issue) – our first TextBox will be set up with the entered text:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
private string enteredText = ""; private SemaphoreSlim semWait = new SemaphoreSlim(0, 1); private async void Box_ChangedFour(object sender, TextChangedEventArgs e) { if (!dontChangeFlag) { TextBox modifedTextBox = sender as TextBox; if (modifedTextBox.Name == "FirstBox") { info = "User modified FirstBox"; enteredText = FirstBox.Text; } else if (modifedTextBox.Name == "SecondBox") info = "User modified SecondBox"; else if (modifedTextBox.Name == "ThirdBox") info = "User modified ThirdBox"; dontChangeFlag = true; // set the lag to prevent next fired event from further changing Boxes FirstBox.Text = enteredText; await semWait.WaitAsync(); // wait until finished changing SecondBox.Text = info; await semWait.WaitAsync(); // wait until finished changing ThirdBox.Text = info; await semWait.WaitAsync(); // wait until finished changing dontChangeFlag = false; // enable changing } else semWait.Release(); } |
Our semaphore should wait until it is released, and it does so, but if we just change the text in the FirstBox then we will see that something is worng – there is no change in other TextBoxes. Quick debug shows that the semaphore waits to be released – what is wrong? Again qucik look at MSDN shows what is going on – If the Text property is set to the same string as the content in the TextBox, the event is not raised. The simpliest way will be to check if the event is going to run:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
private async void Box_ChangedFive(object sender, TextChangedEventArgs e) { if (!dontChangeFlag) { TextBox modifedTextBox = sender as TextBox; if (modifedTextBox.Name == "FirstBox") { info = "User modified FirstBox"; enteredText = FirstBox.Text; } else if (modifedTextBox.Name == "SecondBox") info = "User modified SecondBox"; else if (modifedTextBox.Name == "ThirdBox") info = "User modified ThirdBox"; dontChangeFlag = true; // set the lag to prevent next fired event from further changing Boxes if (FirstBox.Text != enteredText) { FirstBox.Text = info; await semWait.WaitAsync(); // wait until finished changing } if (SecondBox.Text != info) { SecondBox.Text = info; await semWait.WaitAsync(); // wait until finished changing } if (ThirdBox.Text != info) { ThirdBox.Text = info; await semWait.WaitAsync(); // wait until finished changing } } else semWait.Release(); } |
Finally our code is working as it should.
One may ask – what is the purpose of the slider in the picture? This is a simple test showing that not every event is run asynchronous – if we write a similar method like above, but for Slider’s change:
1 2 3 4 5 6 7 8 9 10 11 |
private void mySlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) { if (!dontChangeFlag) { dontChangeFlag = true; mySlider.Value = 50; // below process continues after the second // mySlider_ValueChanged has finished its job dontChangeFlag = false; } } |
and set up breakpoints at lines 3 and 9, then we will see in debug mode that everything works synchronously.
Conclusion: Even if think that you write your code as synchronous, some events/methods can be run as asynchronous, so it’s worth to check them in the documentation.
Complete working example you can download from here.
Each post's copyright held by the original author. All rights reserved.