TextBox.TextChanged event firing twice on Windows Phone 7 emulator

C#XamlSilverlightWindows Phone-7Textbox

C# Problem Overview


I have a very simple test app just to play around with Windows Phone 7. I've just added a TextBox and a TextBlock to the standard UI template. The only custom code is the following:

public partial class MainPage : PhoneApplicationPage
{
    public MainPage()
    {
        InitializeComponent();
    }

    private int counter = 0;

    private void TextBoxChanged(object sender, TextChangedEventArgs e)
    {
        textBlock1.Text += "Text changed " + (counter++) + "\r\n";
    }
}

The TextBox.TextChanged event is wired up to TextBoxChanged in the XAML:

<TextBox Height="72" HorizontalAlignment="Left" Margin="6,37,0,0"
         Name="textBox1" Text="" VerticalAlignment="Top"
         Width="460" TextChanged="TextBoxChanged" />

However, every time I press a key when running in the emulator (either the on-screen keyboard or the physical one, having pressed Pause to enable the latter) it increments the counter twice, displaying two lines in the TextBlock. Everything I've tried shows that the event is genuinely firing twice, and I've no idea why. I've verified that it's only being subscribed once - if I unsubscribe in the MainPage constructor, nothing happens at all (to the text block) when the text changes.

I've tried the equivalent code in a regular Silverlight app, and it didn't occur there. I don't have a physical phone to reproduce this with at the moment. I haven't found any record of this being a known problem in the Windows Phone 7.

Can anyone explain what I'm doing wrong, or should I report this as a bug?

EDIT: To reduce the possibility of this being down to having two text controls, I've tried removing the TextBlock completely, and changing the TextBoxChanged method to just increment counter. I've then run in the emulator, typed 10 letters and then put a breakpoint on the counter++; line (just to get rid of any possibility that breaking into the debugger is causing issues) - and it shows counter as 20.

EDIT: I've now asked in the Windows Phone 7 forum... we'll see what happens.

C# Solutions


Solution 1 - C#

The reason the TextChanged event fires twice in WP7 is a side effect of how the TextBox has been templated for the Metro look.

If you edit the TextBox template in Blend you will see that it contains a secondary TextBox for the disabled/read-only state. This causes, as a side effect, the event to fire twice.

You can change the template to remove the extra TextBox (and associated states) if you don't need these states, or modify the template to achieve a different look in the disabled/read-only state, without using a secondary TextBox.

With that, the event will fire only once.

Solution 2 - C#

i'd go for the bug, mainly because if you put the KeyDown and KeyUp events in there, it shows that that they are fired only once (each of them) but the TextBoxChanged event is fired twice

Solution 3 - C#

That does sound like a bug to me. As a workaround, you could always use Rx's DistinctUntilChanged. There is an overload that allows you to specify the distinct key.

This extension method returns the observable TextChanged event but skips consecutive duplicates:

public static IObservable<IEvent<TextChangedEventArgs>> GetTextChanged(
    this TextBox tb)
{
    return Observable.FromEvent<TextChangedEventArgs>(
               h => textBox1.TextChanged += h, 
               h => textBox1.TextChanged -= h
           )
           .DistinctUntilChanged(t => t.Text);
}

Once the bug is fixed you can simply remove the DistinctUntilChanged line.

Solution 4 - C#

Nice! I found this question by searching for a related problem and also found this annoying thing in my code. Double event eats more CPU resources in my case. So, I fixed my real-time filter textbox with this solution:

private string filterText = String.Empty;

private void SearchBoxUpdated( object sender, TextChangedEventArgs e )
{
	if ( filterText != filterTextBox.Text )
	{
		// one call per change
		filterText = filterTextBox.Text;
		...
	}
	
}

Solution 5 - C#

I believe this has always been a bug in the Compact Framework. It must have been carried over into WP7.

Solution 6 - C#

Sure looks like a bug to me, if you're trying to raise an event every time the text changes you could try using a two-way binding instead, unfortunately this won't raise per-key press change events (only when the field loses focus). Here's a workaround if you need one:

        this.textBox1.TextChanged -= this.TextBoxChanged;
        textBlock1.Text += "Text changed " + (counter++) + "\r\n";
        this.textBox1.TextChanged += this.TextBoxChanged;

Solution 7 - C#

Disclaimer- I'm not familiar with xaml nuances and I know this sounds illogical... but anyway- my first thought is to try passing as just plain eventargs rather than textchangedeventargs. Doesn't make sense, but may be it could help? It seems like when I've seen double firings like this before that it is either due to a bug or due to somehow 2 add event handler calls happening behind the scenes... I'm not sure which though?

If you need quick and dirty, again, me not being experienced with xaml- my next step would be to just skip xaml for that textbox as a quick workaround... do that textbox totally in c# for now until you can pinpoint the bug or tricky code... that is, if you need a temporary solution.

Solution 8 - C#

I dont think it is a bug .. When you assign the value to a text property inside the textchanged event , the textbox value is changed which will again call the text changed event ..

try this in Windows Forms Application , you might get an error

"An unhandled exception of type 'System.StackOverflowException' occurred in System.Windows.Forms.dll"

Solution 9 - C#

StefanWick is right, consider using this template

<Application.Resources>
        <ControlTemplate x:Key="PhoneDisabledTextBoxTemplate" TargetType="TextBox">
            <ContentControl x:Name="ContentElement" BorderThickness="0" HorizontalContentAlignment="Stretch" Margin="{StaticResource PhoneTextBoxInnerMargin}" Padding="{TemplateBinding Padding}" VerticalContentAlignment="Stretch"/>
        </ControlTemplate>
        <Style x:Key="TextBoxStyle1" TargetType="TextBox">
            <Setter Property="FontFamily" Value="{StaticResource PhoneFontFamilyNormal}"/>
            <Setter Property="FontSize" Value="{StaticResource PhoneFontSizeMediumLarge}"/>
            <Setter Property="Background" Value="{StaticResource PhoneTextBoxBrush}"/>
            <Setter Property="Foreground" Value="{StaticResource PhoneTextBoxForegroundBrush}"/>
            <Setter Property="BorderBrush" Value="{StaticResource PhoneTextBoxBrush}"/>
            <Setter Property="SelectionBackground" Value="{StaticResource PhoneAccentBrush}"/>
            <Setter Property="SelectionForeground" Value="{StaticResource PhoneTextBoxSelectionForegroundBrush}"/>
            <Setter Property="BorderThickness" Value="{StaticResource PhoneBorderThickness}"/>
            <Setter Property="Padding" Value="2"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="TextBox">
                        <Grid Background="Transparent">
                            <VisualStateManager.VisualStateGroups>
                                <VisualStateGroup x:Name="CommonStates" ec:ExtendedVisualStateManager.UseFluidLayout="True">
                                    <VisualState x:Name="Normal"/>
                                    <VisualState x:Name="MouseOver"/>
                                    <VisualState x:Name="Disabled">
                                        <Storyboard>
                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="EnabledBorder">
                                                <DiscreteObjectKeyFrame KeyTime="0">
                                                    <DiscreteObjectKeyFrame.Value>
                                                        <Visibility>Collapsed</Visibility>
                                                    </DiscreteObjectKeyFrame.Value>
                                                </DiscreteObjectKeyFrame>
                                            </ObjectAnimationUsingKeyFrames>
                                        </Storyboard>
                                    </VisualState>
                                    <VisualState x:Name="ReadOnly">
                                        <Storyboard>
                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="EnabledBorder">
                                                <DiscreteObjectKeyFrame KeyTime="0">
                                                    <DiscreteObjectKeyFrame.Value>
                                                        <Visibility>Collapsed</Visibility>
                                                    </DiscreteObjectKeyFrame.Value>
                                                </DiscreteObjectKeyFrame>
                                            </ObjectAnimationUsingKeyFrames>
                                        </Storyboard>
                                    </VisualState>
                                </VisualStateGroup>
                                <VisualStateGroup x:Name="FocusStates">
                                    <VisualState x:Name="Focused">
                                        <Storyboard>
                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="EnabledBorder">
                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PhoneTextBoxEditBackgroundBrush}"/>
                                            </ObjectAnimationUsingKeyFrames>
                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" Storyboard.TargetName="EnabledBorder">
                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PhoneTextBoxEditBorderBrush}"/>
                                            </ObjectAnimationUsingKeyFrames>
                                        </Storyboard>
                                    </VisualState>
                                    <VisualState x:Name="Unfocused"/>
                                </VisualStateGroup>
                            </VisualStateManager.VisualStateGroups>
                            <VisualStateManager.CustomVisualStateManager>
                                <ec:ExtendedVisualStateManager/>
                            </VisualStateManager.CustomVisualStateManager>
                            <Border x:Name="EnabledBorder" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Margin="{StaticResource PhoneTouchTargetOverhang}">
                                <ContentControl x:Name="ContentElement" BorderThickness="0" HorizontalContentAlignment="Stretch" Margin="{StaticResource PhoneTextBoxInnerMargin}" Padding="{TemplateBinding Padding}" VerticalContentAlignment="Stretch"/>
                            </Border>
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Application.Resources>

Solution 10 - C#

It's an old topic, but instead of change template (that does not work for me, I dont't see the other textbox with Blend) you can add boolean to check if the event already did the function or not.

boolean already = false;
private void Tweet_SizeChanged(object sender, EventArgs e)
{
	if (!already)
	{
		already = true;
		...
	}
	else
	{
	already = false;
	}
}

I'm aware that is NOT the perfect way, but I think it's the simple way to do that. And it works.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJon SkeetView Question on Stackoverflow
Solution 1 - C#Stefan Wick MSFTView Answer on Stackoverflow
Solution 2 - C#undertakerorView Answer on Stackoverflow
Solution 3 - C#Richard SzalayView Answer on Stackoverflow
Solution 4 - C#crea7orView Answer on Stackoverflow
Solution 5 - C#Jerod HoughtellingView Answer on Stackoverflow
Solution 6 - C#Flatliner DOAView Answer on Stackoverflow
Solution 7 - C#Pimp Juice McJonesView Answer on Stackoverflow
Solution 8 - C#Senthil Kumar BView Answer on Stackoverflow
Solution 9 - C#onmyway133View Answer on Stackoverflow
Solution 10 - C#TDKView Answer on Stackoverflow