web-dev-qa-db-fra.com

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

J'ai une application de test très simple pour jouer avec Windows Phone 7. Je viens d'ajouter un TextBox et un TextBlock au 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";
    }
}

Le TextBox.TextChanged l'événement est connecté à TextBoxChanged dans 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 (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 véritablement deux fois, et je ne sais pas pourquoi. J'ai vérifié qu'il n'est souscrit qu'une seule fois - si je me désabonne dans le constructeur MainPage, 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 Windows Phone 7.

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

EDIT: Pour réduire la possibilité que cela soit dû à deux contrôles de texte, j'ai essayé de supprimer complètement le TextBlock et de changer la méthode TextBoxChanged en juste incrément counter. J'ai ensuite exécuté l'émulateur, tapé 10 lettres et puis mis un point d'arrêt sur le counter++; line (juste pour se débarrasser de toute possibilité que la percée dans le débogueur cause des problèmes) - et il montre counter comme 20.

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

91
Jon Skeet

La raison pour laquelle l'événement TextChanged se déclenche deux fois dans WP7 est un effet secondaire de la façon dont le TextBox a été calqué sur l'apparence Metro.

Si vous modifiez le modèle TextBox dans Blend, vous verrez qu'il contient un TextBox secondaire pour l'état désactivé/lecture seule. Cela provoque, comme effet secondaire, que l'événement se déclenche deux fois.

Vous pouvez changer le modèle pour supprimer le TextBox supplémentaire (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é/en lecture seule, sans utiliser de secondaire TextBox.

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

75
Stefan Wick MSFT

j'irais pour le bogue, principalement parce que si vous y mettez les événements KeyDown et KeyUp, cela montre qu'ils ne sont déclenchés qu'une seule fois (chacun d'eux) mais le TextBoxChanged l'événement est déclenché deux fois

18
undertakeror

Cela ressemble à un bug pour moi. Comme solution de contournement, vous pouvez toujours utiliser DistinctUntilChanged de Rx. Il y a une surcharge qui vous permet de spécifier la clé distincte.

Cette méthode d'extension renvoie l'événement observable TextChanged 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 ligne DistinctUntilChanged.

8
Richard Szalay

Agréable! J'ai trouvé cette question en recherchant un problème connexe et j'ai également trouvé cette chose ennuyeuse dans mon code. L'événement double 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;
        ...
    }

}
2
crea7or

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

1
Jerod Houghtelling

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
onmyway133

Bien sûr, cela ressemble à un bug 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 déclenchera pas d'événements de changement de presse 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;
0
Flatliner DOA

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à fait 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 parfait, mais je pense que c'est le moyen simple de le faire. Et il fonctionne.

0
TDK

Avis de non-responsabilité - Je ne connais pas 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 comme de simples arguments d'événement plutôt que comme textchangedeventargs. Ça n'a pas de sens, mais ça pourrait aider? Il semble que lorsque j'ai vu des déclenchements doubles comme celui-ci auparavant, c'est soit en raison d'un bogue, soit en raison de 2 appels d'ajout de gestionnaire d'événements se produisant dans les coulisses ... Je ne sais pas quoi?

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

0
Pimp Juice McJones

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 texte modifié ..

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"

0
Senthil Kumar B