Zdarzenie TextBox.TextChanged jest uruchamiane dwukrotnie w emulatorze Windows Phone 7

91

Mam bardzo prostą aplikację testową do zabawy z Windows Phone 7. Właśnie dodałem a TextBoxi a TextBlockdo standardowego szablonu UI. Jedyny kod niestandardowy jest następujący:

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

TextBox.TextChangedWydarzenie jest podłączony do TextBoxChangedw XAML:

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

Jednak za każdym razem, gdy naciskam klawisz podczas pracy w emulatorze (na klawiaturze ekranowej lub fizycznej, po naciśnięciu Pause, aby włączyć tę drugą), dwukrotnie zwiększa licznik, wyświetlając dwie linie w TextBlock. Wszystko, czego próbowałem, pokazuje, że zdarzenie jest autentycznie odpalane dwukrotnie i nie mam pojęcia, dlaczego. Sprawdziłem, że jest subskrybowany tylko raz - jeśli wypiszę się w MainPagekonstruktorze, nic się nie dzieje (do bloku tekstowego), gdy tekst się zmienia.

Wypróbowałem równoważny kod w zwykłej aplikacji Silverlight i tam go nie było. W tej chwili nie mam fizycznego telefonu, z którego mógłbym to odtworzyć. Nie znalazłem żadnego zapisu, że jest to znany problem w Windows Phone 7.

Czy ktoś może wyjaśnić, co robię źle, czy powinienem zgłosić to jako błąd?

EDYCJA: Aby zredukować możliwość sprowadzenia tego do dwóch kontrolek tekstu, próbowałem TextBlockcałkowicie usunąć i zmienić metodę TextBoxChanged, aby po prostu zwiększyć counter. Następnie uruchomiłem emulator, wpisałem 10 liter, a następnie umieściłem punkt przerwania w counter++;linii (tylko po to, aby pozbyć się jakiejkolwiek możliwości, że włamanie do debugera powoduje problemy) - i pokazuje się counterjako 20.

EDYCJA: Zapytałem teraz na forum Windows Phone 7 ... zobaczymy, co się stanie.

Jon Skeet
źródło
To tylko z ciekawości - jeśli sprawdzasz wewnątrz wydarzenia, czy zawartość TextBox jest taka sama za każdym razem, gdy zdarzenie jest uruchamiane? Naprawdę nie wiem, dlaczego tak się dzieje, ponieważ zwykle używam MVVM i powiązania danych zamiast obsługi zdarzeń dla tych rzeczy (Silverlight i WPF, niewielkie doświadczenie z WP7).
Rune Jacobsen
@Rune: Tak, dwa razy widzę tekst „po”. Więc jeśli naciśnę "h" i wyświetlę textBox1.Textjako część dodania textBlock1, pokaże "h" w obu wierszach.
Jon Skeet
1
Wspomniałeś o 2 klawiaturach, czy to może mieć znaczenie? Czy możesz wyłączyć jeden? A może możesz sprawdzić, czy wszyscy członkowie TextChangedEventArgs są równi w obu wywołaniach?
Henk Holterman
@Henk: Przez większość czasu nie przejmowałem się włączaniem fizycznej klawiatury ... tylko po to, żeby zobaczyć, czy to przyniesie efekt. TextChangedEventArgstak naprawdę nie ma wielu dostępnych - tylko OriginalSource, co zawsze jest zerowe.
Jon Skeet
3
Wygląda na błąd, nie jest powiązany z klawiaturą, ponieważ możesz uzyskać te same wyniki, po prostu przypisując nową wartość do właściwości Text, TextChanged nadal uruchamia się dwukrotnie.
AnthonyWJones

Odpowiedzi:

75

Powód, dla którego TextChangedzdarzenie uruchamia się dwukrotnie w WP7, jest efektem ubocznym tego, jak TextBoxszablon został utworzony dla wyglądu Metro.

Jeśli edytujesz TextBoxszablon w Blend, zobaczysz, że zawiera on drugorzędny TextBoxstan wyłączony / tylko do odczytu. Powoduje to, jako efekt uboczny, dwukrotne uruchomienie zdarzenia.

Możesz zmienić szablon, aby usunąć dodatkowe TextBox(i powiązane stany), jeśli nie potrzebujesz tych stanów, lub zmodyfikować szablon, aby uzyskać inny wygląd w stanie wyłączonym / tylko do odczytu, bez korzystania z dodatkowego TextBox.

Dzięki temu zdarzenie zostanie uruchomione tylko raz.

Stefan Wick MSFT
źródło
18

Poszedłbym na błąd, głównie dlatego, że jeśli umieścisz tam zdarzenia KeyDowni KeyUp, to pokazuje, że są odpalane tylko raz (każdy z nich), ale TextBoxChangedzdarzenie jest uruchamiane dwa razy

grabarz
źródło
@undertakeror: Dzięki za sprawdzenie tego fragmentu. Zadam to samo pytanie na forum specyficznym dla WP7 i zobaczę, jaka jest odpowiedź ...
Jon Skeet
Co robi TextInput? Wydaje się, że to dość duży błąd, który prześlizgnął się przez testy jednostkowe WP7, ale potem jest SL
Chris S
@Chris S: Co masz na myśli mówiąc „Co robi TextInput?” Nie znam TextInput...
Jon Skeet
@Jon `OnTextInput (TextCompositionEventArgs e)` jest sposobem SL obsługi wprowadzania tekstu zamiast KeyDown, ponieważ oczywiście urządzenie może nie mieć klawiatury: „Występuje, gdy element UI pobiera tekst w sposób niezależny od urządzenia” msdn.microsoft. com / en-us / library /…
Chris S,
Byłem po prostu ciekawy, czy wystrzelił dwa razy
Chris S
8

To brzmi dla mnie jak błąd. Aby obejść ten problem, zawsze możesz użyć Rx DistinctUntilChanged. Istnieje przeciążenie, które umożliwia określenie odrębnego klucza.

Ta metoda rozszerzenia zwraca obserwowalne zdarzenie TextChanged, ale pomija kolejne duplikaty:

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

Po naprawieniu błędu możesz po prostu usunąć DistinctUntilChangedlinię.

Richard Szalay
źródło
2

Ładny! Znalazłem to pytanie, szukając powiązanego problemu, a także znalazłem tę irytującą rzecz w moim kodzie. W moim przypadku zdarzenie podwójne pochłania więcej zasobów procesora. Naprawiłem więc moje pole tekstowe filtru w czasie rzeczywistym za pomocą tego rozwiązania:

private string filterText = String.Empty;

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

}
crea7or
źródło
1

Uważam, że zawsze był to błąd w Compact Framework. Musiało zostać przeniesione do WP7.

Jerod Houghtelling
źródło
Myślałem, że zostało to naprawione w nowszej wersji CF ... i byłoby dziwnie wejść do środka pomimo przejścia na Silverlight. Z drugiej strony, jest to dość dziwne bug, aby zobaczyć w każdym razie ...
Jon Skeet
Zgadzam się, że to dziwne. Ponownie włączyłem się do tego wczoraj w aplikacji CF 2.0.
Jerod Houghtelling
0

Z pewnością wygląda to na błąd, jeśli próbujesz wywołać zdarzenie za każdym razem, gdy zmieni się tekst, możesz zamiast tego spróbować użyć dwukierunkowego wiązania, niestety nie spowoduje to podniesienia zdarzeń zmiany naciśnięcia klawisza (tylko gdy pole traci skupienie). Oto obejście, jeśli potrzebujesz:

        this.textBox1.TextChanged -= this.TextBoxChanged;
        textBlock1.Text += "Text changed " + (counter++) + "\r\n";
        this.textBox1.TextChanged += this.TextBoxChanged;
Flatliner DOA
źródło
Nie jestem pewien, czy to obejdzie problem - problemem nie jest uruchamianie modułu obsługi zdarzeń z powodu textBlock1.Textzmiany - jednak spróbuję. (Obejście ja jechałem spróbować to zrobić mój eventhandler Stateful, pamiętając poprzedni tekst, jeśli nie został faktycznie zmienił go zignorować :).
Jon Skeet
0

Zastrzeżenie - nie jestem zaznajomiony z niuansami XAML i wiem, że brzmi to nielogicznie ... ale w każdym razie - moją pierwszą myślą jest próba podania jako zwykłych elementów zdarzenia, a nie zmienionych tekstowo zdarzeń. To nie ma sensu, ale może mogłoby pomóc? Wygląda na to, że kiedy widziałem już takie podwójne odpalenia, to albo z powodu błędu, albo z powodu 2 dodania wywołań obsługi zdarzeń za kulisami ... Nie jestem jednak pewien, które?

Jeśli potrzebujesz szybkiego i nieczystego, znowu nie mam doświadczenia z xaml - moim następnym krokiem byłoby po prostu pominięcie xaml dla tego pola tekstowego jako szybkiego obejścia ... zrób to pole tekstowe całkowicie w c # na razie, dopóki nie będziesz mógł wskazać błędu lub trudny kod ... to znaczy, jeśli potrzebujesz tymczasowego rozwiązania.

Pimp Juice McJones
źródło
To nie ja przekazuję jakiekolwiek argumenty zdarzeń - implementuję procedurę obsługi zdarzeń. Ale sprawdziłem, że dodanie obsługi zdarzeń wyłącznie w C # nie robi różnicy ... nadal jest uruchamiany dwukrotnie.
Jon Skeet
OK, hmmm. Tak, jeśli to czysty C #, to brzmi bardziej jak błąd. O pierwszej sugestii - przepraszam, że moja werbage była okropna, jak powinienem był stwierdzić - spróbuję [w twojej implementacji / metodzie obsługi TextBoxChanged] zmienić typ parametru args na zwykłe zdarzenia. Prawdopodobnie nie zadziała ... ale hej ... to była tylko moja pierwsza myśl.
Pimp Juice McJones
Innymi słowy, prawdopodobnie to nie zadziała, ale spróbuję podpisania metody = private void TextBoxChanged (nadawca obiektu, EventArgs e) tylko po to, aby powiedzieć, że próbowałem =)
Pimp Juice McJones
Dobrze. Obawiam się, że to nie będzie skuteczne.
Jon Skeet,
0

Nie sądzę, żeby to był błąd. Kiedy przypiszesz wartość do właściwości tekstowej wewnątrz zdarzenia textchanged, wartość pola tekstowego zostanie zmieniona, co ponownie wywoła zdarzenie zmiany tekstu.

spróbuj tego w aplikacji Windows Forms, możesz otrzymać błąd

„W System.Windows.Forms.dll wystąpił nieobsługiwany wyjątek typu„ System.StackOverflowException ””

Senthil Kumar B.
źródło
Z pytania: „Właśnie dodałem TextBox i TextBlock do standardowego szablonu UI” - to nie to samo. Mam jeden TextBox, w którym użytkownik może wpisać, i jeden TextBlock, który wyświetla liczbę.
Jon Skeet
0

StefanWick ma rację, rozważ użycie tego szablonu

<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
źródło
0

To stary temat, ale zamiast zmienić szablon (to nie działa dla mnie, nie widzę drugiego pola tekstowego z Blend) możesz dodać boolean, aby sprawdzić, czy zdarzenie już wykonało funkcję, czy nie.

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

Zdaję sobie sprawę, że to NIE jest idealny sposób, ale myślę, że to prosty sposób. I to działa.

TDK
źródło