TextBox.TextChanged-Ereignis, das zweimal auf dem Windows Phone 7-Emulator ausgelöst wird

91

Ich habe eine sehr einfache Test-App, um mit Windows Phone 7 herumzuspielen. Ich habe gerade ein TextBoxund ein TextBlockzur Standard-UI-Vorlage hinzugefügt . Der einzige benutzerdefinierte Code ist der folgende:

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

Das TextBox.TextChangedEreignis ist TextBoxChangedin der XAML verkabelt :

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

Jedes Mal, wenn ich im Emulator eine Taste drücke (entweder die Bildschirmtastatur oder die physische, nachdem ich Pause gedrückt habe, um letztere zu aktivieren), wird der Zähler zweimal erhöht, wobei zwei Zeilen in der Emulator angezeigt werden TextBlock. Alles, was ich versucht habe, zeigt, dass das Ereignis wirklich zweimal ausgelöst wird, und ich habe keine Ahnung warum. Ich habe überprüft, dass es nur einmal abonniert wird. Wenn ich mich im MainPageKonstruktor abmelde , passiert überhaupt nichts (für den Textblock), wenn sich der Text ändert.

Ich habe den entsprechenden Code in einer regulären Silverlight-App ausprobiert und er ist dort nicht aufgetreten. Ich habe im Moment kein physisches Telefon, mit dem ich das reproduzieren kann. Ich habe keine Aufzeichnung darüber gefunden, dass dies ein bekanntes Problem in Windows Phone 7 ist.

Kann jemand erklären, was ich falsch mache, oder sollte ich dies als Fehler melden?

BEARBEITEN: Um die Wahrscheinlichkeit zu verringern, dass dies auf zwei TextBlockTextsteuerelemente zurückzuführen ist , habe ich versucht, das vollständig zu entfernen und die TextBoxChanged-Methode so zu ändern, dass sie nur inkrementiert wird counter. Ich habe dann den Emulator ausgeführt, 10 Buchstaben eingegeben und dann einen Haltepunkt in die counter++;Zeile gesetzt (nur um die Möglichkeit auszuschließen, dass das Einbrechen in den Debugger Probleme verursacht) - und es wird counter20 angezeigt.

EDIT: Ich habe jetzt im Windows Phone 7-Forum gefragt ... wir werden sehen, was passiert.

Jon Skeet
quelle
Nur aus Interesse - Wenn Sie innerhalb des Ereignisses einchecken, ist der Inhalt der TextBox bei beiden Ereignissen gleich? Ich weiß nicht genau, warum dies passieren würde, da ich normalerweise MVVM und Datenbindung anstelle der Ereignisbehandlung für diese Dinge verwende (Silverlight und WPF, nicht viel Erfahrung mit WP7).
Rune Jacobsen
@ Run: Ja, ich sehe den "Nach" -Text zweimal. Wenn ich also "h" drücke und textBox1.Textals Teil des Zusatzes "textBlock1" anzeige, wird in beiden Zeilen "h" angezeigt .
Jon Skeet
1
Sie erwähnen die 2 Tastaturen, könnte das ein Faktor sein? Können Sie einen deaktivieren? Und vielleicht können Sie überprüfen, ob alle Mitglieder von TextChangedEventArgs in beiden Aufrufen gleich sind?
Henk Holterman
@Henk: Die meiste Zeit habe ich mich nicht darum gekümmert, die physische Tastatur zu aktivieren ... nur um zu sehen, ob sich das auswirken würde. TextChangedEventArgshat nicht wirklich viel zur Verfügung - nur die OriginalSource, die immer null ist.
Jon Skeet
3
Es sieht aus wie ein Fehler, es hängt nicht mit der Tastatur zusammen, da Sie dieselben Ergebnisse erzielen können, indem Sie der Text-Eigenschaft einfach einen neuen Wert zuweisen. Der TextChanged wird immer noch zweimal ausgelöst.
AnthonyWJones

Antworten:

75

Der Grund, warum das TextChangedEreignis in WP7 zweimal ausgelöst wird, ist ein Nebeneffekt der TextBoxVorlage für das Metro-Erscheinungsbild.

Wenn Sie die TextBoxVorlage in Blend bearbeiten, sehen Sie, dass sie eine sekundäre Vorlage TextBoxfür den deaktivierten / schreibgeschützten Status enthält. Dies führt als Nebeneffekt dazu, dass das Ereignis zweimal ausgelöst wird.

Sie können die Vorlage ändern, um die zusätzlichen TextBox(und zugehörigen) Zustände zu entfernen, wenn Sie diese Zustände nicht benötigen, oder die Vorlage ändern, um im deaktivierten / schreibgeschützten Zustand ein anderes Aussehen zu erzielen, ohne einen sekundären zu verwenden TextBox.

Damit wird das Ereignis nur einmal ausgelöst.

Stefan Wick MSFT
quelle
18

Ich würde mich für den Fehler entscheiden, hauptsächlich, weil, wenn Sie die KeyDownund KeyUpEreignisse dort einfügen, es zeigt, dass sie nur einmal (jeder von ihnen) TextBoxChangedausgelöst werden, aber das Ereignis zweimal ausgelöst wird

Bestatter
quelle
@undertakeror: Danke, dass du dir das Stück angesehen hast. Ich werde die gleiche Frage im WP7-spezifischen Forum stellen und sehen, wie die Antwort lautet ...
Jon Skeet
Was macht TextInput? Dies scheint ein ziemlich großer Fehler zu sein, um durch die Unit-Tests des WP7 zu schlüpfen, aber dann ist es SL
Chris S
@ Chris S: Was meinst du mit "Was macht TextInput?" Ich bin nicht vertraut mit TextInput...
Jon Skeet
@Jon `OnTextInput (TextCompositionEventArgs e)` ist die SL-Methode zur Verarbeitung von Texteingaben anstelle von KeyDown, da das Gerät möglicherweise keine Tastatur hat: "Tritt auf, wenn ein UI-Element geräteunabhängig Text abruft
Chris S
Ich war nur neugierig, ob das auch zweimal feuerte
Chris S
8

Das klingt für mich nach einem Fehler. Als Problemumgehung können Sie immer Rx verwenden DistinctUntilChanged. Es gibt eine Überladung, mit der Sie den eindeutigen Schlüssel angeben können.

Diese Erweiterungsmethode gibt das beobachtbare TextChanged-Ereignis zurück, überspringt jedoch aufeinanderfolgende Duplikate:

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

Sobald der Fehler behoben ist, können Sie einfach die DistinctUntilChangedLinie entfernen .

Richard Szalay
quelle
2

Nett! Ich fand diese Frage, indem ich nach einem verwandten Problem suchte, und fand diese nervige Sache auch in meinem Code. Double Event verbraucht in meinem Fall mehr CPU-Ressourcen. Also habe ich mein Echtzeit-Filtertextfeld mit dieser Lösung repariert:

private string filterText = String.Empty;

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

}
crea7or
quelle
1

Ich glaube, das war schon immer ein Fehler im Compact Framework. Es muss in WP7 übertragen worden sein.

Jerod Houghtelling
quelle
Ich dachte, es wurde in einer neueren Version des CF behoben ... und es wäre seltsam, trotz des Wechsels zu Silverlight einzusteigen. Auf der anderen Seite ist es sowieso ein ziemlich seltsamer Fehler ...
Jon Skeet
Ich stimme zu, dass es seltsam ist. Ich habe es gestern in einer CF 2.0-Anwendung wiederholt.
Jerod Houghtelling
0

Sicherlich sieht es für mich nach einem Fehler aus. Wenn Sie versuchen, jedes Mal ein Ereignis auszulösen, wenn sich der Text ändert, können Sie stattdessen eine bidirektionale Bindung verwenden. Leider werden dadurch keine Änderungsereignisse pro Tastendruck ausgelöst (nur wenn das Feld vorhanden ist verliert den Fokus). Hier ist eine Problemumgehung, falls Sie eine benötigen:

        this.textBox1.TextChanged -= this.TextBoxChanged;
        textBlock1.Text += "Text changed " + (counter++) + "\r\n";
        this.textBox1.TextChanged += this.TextBoxChanged;
Flatliner DOA
quelle
Ich bin mir nicht sicher, ob das funktioniert - das Problem ist nicht, dass der Event-Handler aufgrund von textBlock1.TextÄnderungen ausgelöst wird - ich werde es trotzdem versuchen. (Die Problemumgehung, die ich versuchen wollte, bestand darin, meinen Eventhandler zustandsbehaftet zu machen und sich an den vorherigen Text zu erinnern. Wenn er sich nicht wirklich geändert hat, ignorieren Sie ihn :)
Jon Skeet
0

Haftungsausschluss - Ich bin mit XAML-Nuancen nicht vertraut und ich weiß, dass dies unlogisch klingt ... aber trotzdem - mein erster Gedanke ist, zu versuchen, nur als einfache Eventargs und nicht als Textchangedeventargs zu gelten. Macht keinen Sinn, aber könnte es helfen? Es scheint, als ob ich solche Doppelschüsse schon einmal gesehen habe, dass dies entweder auf einen Fehler oder auf 2 zusätzliche Event-Handler-Aufrufe zurückzuführen ist, die hinter den Kulissen stattfinden ... Ich bin mir nicht sicher, welche?

Wenn Sie schnell und schmutzig sein müssen, habe ich keine Erfahrung mit xaml. Mein nächster Schritt wäre, xaml für dieses Textfeld als schnelle Problemumgehung zu überspringen. Führen Sie dieses Textfeld vorerst vollständig in c # aus, bis Sie den Fehler oder kniffliger Code ... das heißt, wenn Sie eine temporäre Lösung benötigen.

Zuhälter-Saft McJones
quelle
Ich bin nicht derjenige, der Event-Argumente übergibt - ich implementiere einen Event-Handler. Aber ich habe überprüft, dass das Hinzufügen des Ereignishandlers nur in C # keinen Unterschied macht ... er wird immer noch zweimal ausgelöst.
Jon Skeet
OK, hmmm. Ja, wenn es reines c # ist, dann klingt es eher nach einem Bug. Ungefähr durch den ersten Vorschlag - es tut mir leid, dass meine Aussprache schrecklich war, wie ich hätte sagen sollen - würde ich versuchen [in Ihrer Implementierung / TextBoxChanged-Handler-Methode], den args-Parametertyp in einfache Eventargs zu ändern. Wird wahrscheinlich nicht funktionieren ... aber hey ... es war nur mein erster Gedanke.
Pimp Juice McJones
Mit anderen Worten, es wird wahrscheinlich nicht funktionieren, aber ich würde versuchen, Methode Signatur = private void TextBoxChanged (Objekt Absender, EventArgs e) nur um zu sagen, dass ich es versucht habe =)
Pimp Juice McJones
Richtig. Ich fürchte, das wird keine Wirkung haben, fürchte ich.
Jon Skeet
0

Ich glaube nicht, dass es ein Fehler ist. Wenn Sie den Wert einer Texteigenschaft innerhalb des textveränderten Ereignisses zuweisen, wird der Textfeldwert geändert, wodurch das textveränderte Ereignis erneut aufgerufen wird.

Versuchen Sie dies in der Windows Forms-Anwendung. Möglicherweise wird eine Fehlermeldung angezeigt

"In System.Windows.Forms.dll ist eine nicht behandelte Ausnahme vom Typ 'System.StackOverflowException' aufgetreten."

Senthil Kumar B.
quelle
Aus der Frage: "Ich habe gerade eine TextBox und einen TextBlock zur Standard-UI-Vorlage hinzugefügt" - sie sind nicht dasselbe. Ich habe eine TextBox, in die der Benutzer eingeben kann, und eine TextBlock, die die Anzahl anzeigt.
Jon Skeet
0

StefanWick hat recht, erwägen Sie die Verwendung dieser Vorlage

<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
quelle
0

Es ist ein altes Thema, aber anstatt die Vorlage zu ändern (das funktioniert bei mir nicht, ich sehe das andere Textfeld mit Blend nicht), können Sie einen Booleschen Wert hinzufügen, um zu überprüfen, ob das Ereignis die Funktion bereits ausgeführt hat oder nicht.

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

Ich bin mir bewusst, dass dies NICHT der perfekte Weg ist, aber ich denke, es ist der einfache Weg, dies zu tun. Und es funktioniert.

TDK
quelle