Événement TextBox.TextChanged se déclenchant deux fois sur l'émulateur Windows Phone 7


91

J'ai une application de test très simple pour jouer avec Windows Phone 7. Je viens d'ajouter un TextBoxet un TextBlockau modèle d'interface utilisateur standard. Le seul code personnalisé est le suivant:

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

L' TextBox.TextChangedévénement est câblé TextBoxChangeddans le XAML:

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

Cependant, chaque fois que j'appuie sur une touche lors de l'exécution dans l'émulateur (que ce soit le clavier à l'écran ou le clavier physique, après avoir appuyé sur Pause pour activer ce dernier), il incrémente le compteur deux fois, affichant deux lignes dans le TextBlock. Tout ce que j'ai essayé montre que l'événement se déclenche vraiment deux fois, et je ne sais pas pourquoi. J'ai vérifié qu'il n'est abonné qu'une seule fois - si je me désinscris dans le MainPageconstructeur, rien ne se passe du tout (au bloc de texte) lorsque le texte change.

J'ai essayé le code équivalent dans une application Silverlight standard, et cela ne s'est pas produit là-bas. Je n'ai pas de téléphone physique pour reproduire cela pour le moment. Je n'ai trouvé aucune trace de ce problème connu dans le Windows Phone 7.

Quelqu'un peut-il expliquer ce que je fais mal ou dois-je signaler cela comme un bogue?

EDIT: Pour réduire la possibilité que cela se résume à deux contrôles de texte, j'ai essayé de supprimer TextBlockcomplètement le et de changer la méthode TextBoxChanged pour simplement incrémenter counter. J'ai ensuite exécuté l'émulateur, tapé 10 lettres, puis mis un point d'arrêt sur la counter++;ligne (juste pour se débarrasser de toute possibilité que le fait de pénétrer dans le débogueur cause des problèmes) - et cela apparaît countercomme 20.

EDIT: J'ai maintenant demandé dans le forum Windows Phone 7 ... nous verrons ce qui se passe.


Juste par intérêt - si vous vérifiez à l'intérieur de l'événement, le contenu de la zone de texte est-il le même les deux fois que l'événement se déclenche? Je ne sais pas vraiment pourquoi cela se produirait, car j'utilise généralement MVVM et la liaison de données au lieu de la gestion des événements pour ces choses (Silverlight et WPF, pas beaucoup d'expérience avec WP7).
Rune Jacobsen

@Rune: Oui, je vois le texte "après" deux fois. Donc, si j'appuie sur "h" et textBox1.Textque j'affiche dans le cadre de l'addition textBlock1, il affichera "h" dans les deux lignes.
Jon Skeet

1
Vous mentionnez les 2 claviers, cela pourrait-il être un facteur? Pouvez-vous en désactiver un? Et peut-être pouvez-vous vérifier si tous les membres de TextChangedEventArgs sont égaux dans les deux appels?
Henk Holterman

@Henk: La plupart du temps, je n'ai pas pris la peine d'activer le clavier physique ... seulement pour voir si cela aurait un effet. TextChangedEventArgsn'a pas vraiment beaucoup de disponible - juste le OriginalSource, qui est toujours nul.
Jon Skeet

3
Cela ressemble à un bogue, il n'est pas lié au clavier car vous pouvez obtenir les mêmes résultats en attribuant simplement une nouvelle valeur à la propriété Text, le TextChanged se déclenche toujours deux fois.
AnthonyWJones

Réponses:


75

La raison pour laquelle l' TextChangedévénement se déclenche deux fois dans WP7 est un effet secondaire de la façon dont le TextBoxa été conçu pour le look Metro.

Si vous modifiez le TextBoxmodèle dans Blend, vous verrez qu'il contient un secondaire TextBoxpour l'état désactivé / en lecture seule. Cela provoque, comme effet secondaire, l'événement à se déclencher deux fois.

Vous pouvez modifier le modèle pour supprimer le supplément TextBox(et les états associés) si vous n'avez pas besoin de ces états, ou modifier le modèle pour obtenir un aspect différent dans l'état désactivé / lecture seule, sans utiliser de secondaire TextBox.

Avec cela, l'événement ne se déclenchera qu'une seule fois.


18

J'irais pour le bogue, principalement parce que si vous mettez les événements KeyDownet KeyUplà-dedans, cela montre qu'ils ne sont déclenchés qu'une seule fois (chacun d'eux) mais l' TextBoxChangedévénement est déclenché deux fois


@undertakeror: Merci d'avoir vérifié ce morceau. Je vais poser la même question sur le forum spécifique à WP7 et voir quelle est la réponse ...
Jon Skeet

Que fait TextInput? Cela semble être un gros bug à glisser dans les tests unitaires du WP7, mais alors c'est SL
Chris S

@Chris S: Que voulez-vous dire par "Que fait TextInput-il?" Je ne connais pas TextInput...
Jon Skeet

@Jon `OnTextInput (TextCompositionEventArgs e)` est la manière SL de gérer la saisie de texte au lieu de KeyDown, car il est évident que le périphérique peut ne pas avoir de clavier: "Se produit lorsqu'un élément de l'interface utilisateur obtient du texte de manière indépendante du périphérique" msdn.microsoft. com / en-us / library /…
Chris S

J'étais juste curieux de savoir si cela a tiré deux fois aussi
Chris S

8

Cela me semble être un bug. Pour contourner le problème, vous pouvez toujours utiliser Rx DistinctUntilChanged. Il existe une surcharge qui vous permet de spécifier la clé distincte.

Cette méthode d'extension renvoie l'événement TextChanged observable mais ignore les doublons consécutifs:

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

Une fois le bogue corrigé, vous pouvez simplement supprimer la DistinctUntilChangedligne.


2

Agréable! J'ai trouvé cette question en recherchant un problème connexe et j'ai également trouvé cette chose ennuyeuse dans mon code. Le double événement consomme plus de ressources CPU dans mon cas. J'ai donc corrigé ma zone de texte de filtre en temps réel avec cette solution:

private string filterText = String.Empty;

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

}

1

Je pense que cela a toujours été un bogue dans le Compact Framework. Il doit avoir été transféré dans WP7.


Je pensais que c'était corrigé dans une version plus récente du CF ... et ce serait bizarre d'entrer malgré le passage à Silverlight. D'un autre côté, c'est un bug assez bizarre à voir quand même ...
Jon Skeet

Je conviens que c'est étrange. J'y suis revenu hier dans une application CF 2.0.
Jerod Houghtelling

0

Bien sûr, cela ressemble à un bogue pour moi, si vous essayez de déclencher un événement à chaque fois que le texte change, vous pouvez essayer d'utiliser une liaison bidirectionnelle à la place, malheureusement cela ne soulèvera pas d'événements de changement de pression par touche (uniquement lorsque le champ perd le focus). Voici une solution de contournement si vous en avez besoin:

        this.textBox1.TextChanged -= this.TextBoxChanged;
        textBlock1.Text += "Text changed " + (counter++) + "\r\n";
        this.textBox1.TextChanged += this.TextBoxChanged;

Je ne suis pas sûr que cela puisse contourner le problème - le problème n'est pas le déclenchement du gestionnaire d'événements à cause du textBlock1.Textchangement - je vais cependant essayer. (La solution de contournement que j'allais essayer était de rendre mon gestionnaire d'événements avec état, en se souvenant du texte précédent. S'il n'a pas réellement changé, ignorez-le :)
Jon Skeet

0

Clause de non-responsabilité - Je ne suis pas familier avec les nuances xaml et je sais que cela semble illogique ... mais de toute façon - ma première pensée est d'essayer de passer simplement comme des eventargs plutôt que comme des textchangedeventargs. Cela n'a pas de sens, mais peut-être que cela pourrait aider? Il semble que lorsque j'ai déjà vu des doubles déclenchements comme celui-ci, c'est soit à cause d'un bogue, soit à cause de 2 appels de gestionnaire d'événements d'ajout se déroulant dans les coulisses ... Je ne sais pas lequel?

Si vous avez besoin de rapide et de sale, encore une fois, je n'ai pas d'expérience avec xaml - ma prochaine étape serait de simplement ignorer xaml pour cette zone de texte comme solution de contournement rapide ... faites cette zone de texte totalement en c # pour le moment jusqu'à ce que vous puissiez localiser le bogue ou code délicat ... c'est-à-dire si vous avez besoin d'une solution temporaire.


Je ne suis pas celui qui passe des arguments d'événement - j'implémente un gestionnaire d'événements. Mais j'ai vérifié que l'ajout du gestionnaire d'événements uniquement en C # ne fait aucune différence ... il est toujours déclenché deux fois.
Jon Skeet

OK, hmmm. Ouais, si c'est du c # pur, ça ressemble plus à un bug. À propos de la première suggestion - je suis désolé mon verbage était horrible, comment j'aurais dû le dire - j'essaierais [dans votre méthode de gestion de mise en œuvre / TextBoxChanged] de changer le type de paramètre args en uniquement des eventargs. Ça ne marchera probablement pas ... mais bon ... c'était juste ma première pensée.
Pimp Juice McJones

En d'autres termes, cela ne fonctionnera probablement pas mais j'essaierais la méthode signature = private void TextBoxChanged (expéditeur de l'objet, EventArgs e) juste pour dire que je l'ai essayé =)
Pimp Juice McJones

Droite. Je ne pense pas que cela sera efficace, j'en ai peur.
Jon Skeet

0

Je ne pense pas que ce soit un bogue. Lorsque vous attribuez la valeur à une propriété de texte à l'intérieur de l'événement textchanged, la valeur de la zone de texte est modifiée, ce qui appellera à nouveau l'événement text changed.

essayez ceci dans l'application Windows Forms, vous pourriez obtenir une erreur

«Une exception non gérée de type 'System.StackOverflowException' s'est produite dans System.Windows.Forms.dll»


De la question: "Je viens d'ajouter un TextBox et un TextBlock au modèle d'interface utilisateur standard" - ce n'est pas la même chose. J'ai un TextBox dans lequel l'utilisateur peut taper et un TextBlock qui affiche le nombre.
Jon Skeet

0

StefanWick a raison, pensez à utiliser ce modèle

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

0

C'est un vieux sujet, mais au lieu de changer de modèle (cela ne fonctionne pas pour moi, je ne vois pas l'autre zone de texte avec Blend), vous pouvez ajouter un booléen pour vérifier si l'événement a déjà rempli la fonction ou non.

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

Je suis conscient que ce n'est PAS le moyen idéal, mais je pense que c'est le moyen le plus simple de le faire. Et il fonctionne.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.