93

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.

Stefan Wick MSFT
  • 13,600
  • 1
  • 32
  • 51
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Just out of interest - if you check inside the event, is the content of the TextBox the same both times the event fires? I don't really know why this would happen, as I usually use MVVM and data binding instead of event handling for these things (Silverlight and WPF, not much experience with WP7). – Rune Jacobsen Aug 09 '10 at 09:51
  • @Rune: Yes, I see the "after" text twice. So if I press "h" and display `textBox1.Text` as part of the textBlock1 addition, it will show "h" in both lines. – Jon Skeet Aug 09 '10 at 09:55
  • 1
    You mention the 2 keyboards, could that be a factor? Can you disable one? And maybe can you can check if all members of TextChangedEventArgs are equal in both calls? – H H Aug 09 '10 at 10:01
  • @Henk: Most of the time I haven't bothered enabling the physical keyboard... only to see if that would have an effect. `TextChangedEventArgs` doesn't really have a lot available - just the `OriginalSource`, which is always null. – Jon Skeet Aug 09 '10 at 10:04
  • 3
    It does look like a bug, it isn't related to the keyboard because you can get the same results by simply assigning a new value to the Text property, the TextChanged still fires twice. – AnthonyWJones Aug 09 '10 at 12:15
  • Are you typing with two keyboards again? – Chris S Aug 09 '10 at 12:34
  • @Richard: Ooh, I haven't checked yet. Will do so today. – Jon Skeet Sep 28 '10 at 07:02
  • What happens when you set e.Handled = true? – Wim Bokkers Aug 09 '10 at 11:13
  • `TextChangedEventArgs` doesn't have a `Handled` property in WP7 :( – Jon Skeet Aug 09 '10 at 11:14
  • @JonSkeet Hi, Is this bug fixed already at this time? Thanks – Allan Chua Jan 02 '12 at 09:03
  • @AllanChua: I haven't done any Mango development, so it may be fixed there - but I'm not sure. – Jon Skeet Jan 02 '12 at 10:19
  • @JonSkeet uhmm guess i have to check it out :).. by the way thanks for the time sir.. – Allan Chua Jan 03 '12 at 00:27
  • In my windows phone 8 real device this works fine. – adrian4aes Jan 30 '15 at 20:05

10 Answers10

76

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.

Smi
  • 13,850
  • 9
  • 56
  • 64
Stefan Wick MSFT
  • 13,600
  • 1
  • 32
  • 51
18

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

undertakeror
  • 1,022
  • 7
  • 11
  • @undertakeror: Thanks for checking out that bit. I'll ask the same question on the WP7-specific forum and see what the response is... – Jon Skeet Aug 09 '10 at 11:45
  • What does TextInput do? These seems like quite a big bug to slip through the unit tests of the WP7, but then it is SL – Chris S Aug 09 '10 at 12:36
  • @Chris S: What do you mean by "What does `TextInput` do?" I'm not familiar with `TextInput`... – Jon Skeet Aug 09 '10 at 12:42
  • @Jon ` OnTextInput(TextCompositionEventArgs e)` is the SL way of handling text input instead of KeyDown, as obviously the device may not have a keyboard: "Occurs when a UI element gets text in a device-independent manner" http://msdn.microsoft.com/en-us/library/system.windows.uielement.textinput(v=VS.95).aspx – Chris S Aug 09 '10 at 15:27
  • I was just curious if that fired twice as well – Chris S Aug 09 '10 at 15:27
  • @Chris: I'll give it a try if I have time :) – Jon Skeet Aug 10 '10 at 16:49
8

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.

Richard Szalay
  • 83,269
  • 19
  • 178
  • 237
2

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;
        ...
    }

}
crea7or
  • 4,421
  • 2
  • 26
  • 37
1

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

Jerod Houghtelling
  • 4,783
  • 1
  • 22
  • 30
  • I thought it was fixed in a more recent version of the CF... and it would be weird to get in despite the move to Silverlight. On the other hand, it's a pretty weird bug to see anyway... – Jon Skeet Aug 11 '10 at 15:37
  • I agree that it is strange. I reran into it yesterday in a CF 2.0 application. – Jerod Houghtelling Aug 11 '10 at 15:57
0

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>
onmyway133
  • 45,645
  • 31
  • 257
  • 263
0

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.

TDK
  • 161
  • 2
  • 13
0

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;
Flatliner DOA
  • 6,128
  • 4
  • 30
  • 39
  • I'm not sure that that will work around it - the problem isn't the event handler firing because of `textBlock1.Text` changing - I'll give it a try though. (The workaround *I* was going to try was to make my eventhandler stateful, remembering the previous text. If it hasn't actually changed, ignore it :) – Jon Skeet Aug 09 '10 at 10:38
0

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.

  • I'm not the one passing any event args - I'm implementing an event handler. But I've verified that adding the event handler purely in C# makes no difference... it still gets fired twice. – Jon Skeet Aug 09 '10 at 14:45
  • OK, hmmm. Yeah, if it's pure c# then it sounds more like a bug. About by first suggestion- I'm sorry my verbage was horrible, how I should have stated is- I'd try [in your implementation/TextBoxChanged handler method] change the args parameter type to just plain eventargs. Probably won't work... but hey... it was just my first thought. – Pimp Juice McJones Aug 09 '10 at 15:02
  • In other words, it probably won't work but I'd try method signature = private void TextBoxChanged(object sender, EventArgs e) just to say that I tried it =) – Pimp Juice McJones Aug 09 '10 at 15:06
  • Right. I don't think that will have any effective, I'm afraid. – Jon Skeet Aug 09 '10 at 16:27
0

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"

Senthil Kumar B
  • 926
  • 6
  • 13
  • From the question: "I've just added a TextBox and a TextBlock to the standard UI template" - they're not the same thing. I've got one TextBox which the user can type into, and one TextBlock which displays the count. – Jon Skeet Jan 18 '11 at 06:27