Fixing TextChanged event firing twice in Windows Phone 7 (WP7)

I’m currently playing around with WP7 and WP8 development trying to get to grips with the framework. I don’t have a specific application in mind but I’m trying to create a basic application that allows me take notes on the fly. Nothing fancy there.

One of the “requirements” is to be able to search across all my notes for a specific word and get all the results back in one continuous list that will contain the name/title of the note that matches the search criteria. In my effort to be fancy, I decided against using a separate button. Instead, I wanted to be able to initiate a search once the textbox had 2 or more characters, sort of an autocomplete textbox.

So my page looks like this:
SearchScreen

The xaml code for this page is listed below:

… (omitted for brevity)
 <Grid x:Name="ContentPanel" Margin="12,15,12,0" Grid.Row="1">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"></RowDefinition>
        <RowDefinition Height="Auto"></RowDefinition>
        <RowDefinition Height="*"></RowDefinition>
    </Grid.RowDefinitions>
    <TextBlock Name="TblSearchDesc" Text="Start typing to initiate a note search" Margin="12,0,0,-5"></TextBlock>
    <TextBox Name="TxtSearchTerm" Grid.Row="1" HorizontalAlignment="Stretch"
             Margin="0,0,0,0"
             TextChanged="TxtSearchTermTextChanged"
    </TextBox>
    <!--<Button x:Name="BtnLogin" HorizontalAlignment="Right"
            Content="Search"
            Click="BtnSearchClick" Width="124" Margin="0,0,-19,0"/>-->
    <StackPanel Grid.Row="2">
        <ListBox x:Name="PasswordList" 
                 ScrollViewer.HorizontalScrollBarVisibility="Auto"
                 Height="510">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock Grid.Column="0"
                                HorizontalAlignment="Stretch"
                                Text="{Binding Name}" 
                                FontSize="{StaticResource PhoneFontSizeLarge}"
                                FontWeight="Normal" Margin="12,5,0,0">
                    </TextBlock>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </StackPanel>
</Grid>  

The important part is highlighted. A normal TextBox with no special properties other than alignment and a method to handle the TextChanged event. To quickly test the method for the TextChange event I added a MessageBox.Show(“hello”); line.

I compiled and run the code, navigated to the page and started typing. This immediately brought to the surface an existing WP7 bug where the TextChanged event fires off twice. The reason behind it is fairly simple - a bug - and Stefan Wick  was kind enough to give a great explanation on StackOverflow here on why this is happening. In summary, the TextBox contains two (yes, that’s 2) text controls, the second being there to handle the disabled/read-only state. Hence, the event first twice, once for each control within the template.

The solution is simple, as long as your textbox doesn’t need a disabled/read-only state. First you need to create a new template by copying the existing one and then you need to remove all the xaml that relates to the second disabled/read-only textbox. Finally, you will need to add this new resource to the appropriate textbox(es). If you don’t feel confident messing with xaml templates (neither do I), just copy/paste the code below to the top of your page:

<phone:PhoneApplicationPage.Resources>  
    <Style x:Key="SimpleTextBoxStyle" 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">
                                <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>
                        <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>
</phone:PhoneApplicationPage.Resources>  

Ensure you edit your TextBox to use the new template resource similarly to this:

<TextBox Name="TxtSearchTerm"  
     Grid.Row="1" 
     HorizontalAlignment="Stretch"
     Margin="0,0,0,0" 
     TextChanged="TxtSearchTermTextChanged" 
     Style="{StaticResource SimpleTextBoxStyle}">
</TextBox>  

This workaround will stop the TextChanged event firing twice.

Happy coding…


  • Share this post on
comments powered by Disqus