views:

909

answers:

8

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.

A: 

In your handler you assign a value to the Text property which means that the Text property changes. So the event should fire. This is what all WPF / Silverlight controls do.

If you have a look at the WPF Toolkit (I haven't checked Silverlight Toolkit) you can see that what they do is insert a counter around the assignment. What they do is check that it isn't above zero before handling the event.

  • Text = "something"
  • OnTextChanged fires
  • Check if internal textchanged count == 0, otherwise return
  • Add one to internal textchanged count
  • Set new text
  • Second TextChanged event fires (but will not go past the internal text changed count)
  • Subtract one from internal textchanged count
  • Exit
jjrdk
@jjrdk: No, I'm changing the Text property of the text *block*, not the text *box*. It's the text *box* that has the event handler attached. The event handler doesn't change the text in the text *box*, so the event shouldn't fire twice.
Jon Skeet
A: 

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
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
A: 

What happens when you set e.Handled = true?

Wim Bokkers
@Wim: `TextChangedEventArgs` doesn't have a `Handled` property in WP7 :(
Jon Skeet
+10  A: 

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
@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
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
@Chris S: What do you mean by "What does `TextInput` do?" I'm not familiar with `TextInput`...
Jon Skeet
@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
I was just curious if that fired twice as well
Chris S
@Chris: I'll give it a try if I have time :)
Jon Skeet
A: 

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.

Pimp Juice McJones
@Pimp: 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
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
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
@Pimp: Right. I don't think that will have any effective, I'm afraid.
Jon Skeet
+1  A: 

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

Jerod Houghtelling
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
I agree that it is strange. I reran into it yesterday in a CF 2.0 application.
Jerod Houghtelling
+1  A: 

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