L'evento TextBox.TextChanged viene attivato due volte sull'emulatore di Windows Phone 7


91

Ho un'app di test molto semplice solo per giocare con Windows Phone 7. Ho appena aggiunto un TextBoxe un TextBlockal modello di interfaccia utente standard. L'unico codice personalizzato è il seguente:

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.TextChangedevento è cablato TextBoxChangednel XAML:

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

Tuttavia, ogni volta che premo un tasto durante l'esecuzione nell'emulatore (sia la tastiera su schermo che quella fisica, avendo premuto Pausa per abilitare quest'ultima) incrementa il contatore due volte, visualizzando due righe nel file TextBlock. Tutto quello che ho provato mostra che l'evento si sta realmente verificando due volte, e non ho idea del perché. Ho verificato che viene sottoscritto solo una volta: se annullo l'iscrizione nel MainPagecostruttore, non accade nulla (al blocco di testo) quando il testo cambia.

Ho provato il codice equivalente in una normale app Silverlight e non si è verificato lì. Al momento non ho un telefono fisico con cui riprodurlo. Non ho trovato alcuna traccia di questo problema noto in Windows Phone 7.

Qualcuno può spiegare cosa sto facendo di sbagliato o devo segnalarlo come un bug?

EDIT: Per ridurre la possibilità di questo essere giù ad avere due controlli di testo, ho cercato di rimuovere il TextBlocktutto, e cambiando il metodo TextBoxChanged al solo incremento counter. Ho quindi eseguito l'emulatore, ho digitato 10 lettere e quindi ho inserito un punto di interruzione sulla counter++;riga (solo per eliminare qualsiasi possibilità che l' interruzione del debugger stia causando problemi) - e viene visualizzato countercome 20.

EDIT: ora l'ho chiesto nel forum di Windows Phone 7 ... vedremo cosa succede.


Solo per interesse: se controlli all'interno dell'evento, il contenuto del TextBox è lo stesso entrambe le volte che l'evento si attiva? Non so davvero perché ciò accada, poiché di solito uso MVVM e l'associazione dei dati invece della gestione degli eventi per queste cose (Silverlight e WPF, poca esperienza con WP7).
Rune Jacobsen

@ Rune: Sì, vedo il testo "dopo" due volte. Quindi se premo "h" e visualizzo textBox1.Textcome parte dell'aggiunta textBlock1, mostrerà "h" in entrambe le righe.
Jon Skeet

1
Hai menzionato le 2 tastiere, potrebbe essere un fattore? Puoi disabilitarne uno? E forse puoi controllare se tutti i membri di TextChangedEventArgs sono uguali in entrambe le chiamate?
Henk Holterman

@ Henk: La maggior parte delle volte non mi sono preoccupato di abilitare la tastiera fisica ... solo per vedere se questo avrebbe avuto effetto. TextChangedEventArgsnon ha molto a disposizione - solo il OriginalSource, che è sempre nullo.
Jon Skeet

3
Sembra un bug, non è correlato alla tastiera perché puoi ottenere gli stessi risultati semplicemente assegnando un nuovo valore alla proprietà Text, TextChanged si attiva ancora due volte.
AnthonyWJones

Risposte:


75

Il motivo per cui l' TextChangedevento si attiva due volte in WP7 è un effetto collaterale del modo in cui TextBoxè stato modellato l'aspetto di Metro.

Se modifichi il TextBoxmodello in Blend, vedrai che contiene un secondario TextBoxper lo stato disabilitato / di sola lettura. Ciò fa sì che l'evento si attivi due volte.

È possibile modificare il modello per rimuovere gli stati aggiuntivi TextBox(e associati) se non sono necessari questi stati o modificare il modello per ottenere un aspetto diverso nello stato disabilitato / di sola lettura, senza utilizzare un secondario TextBox.

Con ciò, l'evento si attiverà solo una volta.


18

preferirei il bug, principalmente perché se metti gli eventi KeyDowne KeyUplì, mostra che vengono attivati ​​solo una volta (ciascuno di essi) ma l' TextBoxChangedevento viene attivato due volte


@undertakeror: Grazie per aver controllato quella parte. Farò la stessa domanda sul forum specifico di WP7 e vedrò qual è la risposta ...
Jon Skeet

Cosa fa TextInput? Sembra un bug abbastanza grosso da superare negli unit test del WP7, ma poi è SL
Chris S

@ Chris S: Cosa intendi con "Cosa fa TextInput?" Non ho familiarità con TextInput...
Jon Skeet

@ Jon `OnTextInput (TextCompositionEventArgs e)` è il modo SL di gestire l'input di testo invece di KeyDown, poiché ovviamente il dispositivo potrebbe non avere una tastiera: "Si verifica quando un elemento dell'interfaccia utente riceve il testo in modo indipendente dal dispositivo" msdn.microsoft. com / en-us / library /…
Chris S

Ero solo curioso di sapere se anche questo ha sparato due volte
Chris S

8

Mi sembra un insetto. Come soluzione alternativa, puoi sempre usare Rx's DistinctUntilChanged. È presente un sovraccarico che consente di specificare la chiave distinta.

Questo metodo di estensione restituisce l'evento TextChanged osservabile ma salta i duplicati consecutivi:

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

Una volta risolto il bug, puoi semplicemente rimuovere la DistinctUntilChangedlinea.


2

Bello! Ho trovato questa domanda cercando un problema correlato e ho anche trovato questa cosa fastidiosa nel mio codice. Il doppio evento consuma più risorse della CPU nel mio caso. Quindi, ho corretto la mia casella di testo del filtro in tempo reale con questa soluzione:

private string filterText = String.Empty;

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

}

1

Credo che questo sia sempre stato un bug nel Compact Framework. Deve essere stato trasferito nel WP7.


Pensavo fosse stato risolto in una versione più recente del CF ... e sarebbe stato strano entrare nonostante il passaggio a Silverlight. D'altra parte, è un bug piuttosto strano da vedere comunque ...
Jon Skeet

Sono d'accordo che è strano. Ci ho riprovato ieri in un'applicazione CF 2.0.
Jerod Houghtelling

0

Sicuramente mi sembra un bug, se stai cercando di generare un evento ogni volta che il testo cambia, potresti provare a usare invece un'associazione a due vie, sfortunatamente questo non solleverà eventi di modifica della pressione per tasto (solo quando il campo perde la concentrazione). Ecco una soluzione alternativa se ne hai bisogno:

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

Non sono sicuro che questo funzionerà - il problema non è l'attivazione del gestore di eventi a causa del textBlock1.Textcambiamento - ci proverò comunque. (La soluzione alternativa che stavo per provare era rendere stateful il mio gestore di eventi, ricordando il testo precedente. Se non è effettivamente cambiato, ignoralo :)
Jon Skeet

0

Disclaimer- Non ho familiarità con le sfumature xaml e so che questo suona illogico ... ma comunque- il mio primo pensiero è provare a passare come semplici eventargs piuttosto che textchangedeventargs. Non ha senso, ma potrebbe essere d'aiuto? Sembra che quando ho visto doppie cotture come questa in passato sia dovuto a un bug o dovuto in qualche modo a 2 chiamate add al gestore di eventi che si verificano dietro le quinte ... Non sono sicuro di quale però?

Se hai bisogno di veloce e sporco, ancora una volta, non ho esperienza con xaml- il mio prossimo passo sarebbe saltare semplicemente xaml per quella casella di testo come soluzione rapida ... fai quella casella di testo totalmente in c # per ora fino a quando non puoi individuare il bug o codice complicato ... cioè, se hai bisogno di una soluzione temporanea.


Non sono io a passare alcun argomento evento: sto implementando un gestore di eventi. Ma ho verificato che l'aggiunta del gestore eventi esclusivamente in C # non fa differenza ... viene comunque attivato due volte.
Jon Skeet

OK, hmmm. Sì, se è puro c #, suona più come un bug. Circa per primo suggerimento - Mi dispiace che il mio verbage fosse orribile, come avrei dovuto affermarlo - Proverei [nella tua implementazione / metodo del gestore TextBoxChanged] a cambiare il tipo di parametro args in semplici eventargs. Probabilmente non funzionerà ... ma hey ... è stato solo il mio primo pensiero.
Pimp Juice McJones

In altre parole, probabilmente non funzionerà, ma proverei la firma del metodo = private void TextBoxChanged (object sender, EventArgs e) solo per dire che l'ho provato =)
Pimp Juice McJones

Destra. Non credo che avrà alcun effetto, temo.
Jon Skeet

0

Non penso che sia un bug .. Quando assegni il valore a una proprietà di testo all'interno dell'evento textchanged, il valore della casella di testo viene modificato che chiamerà nuovamente l'evento di modifica del testo ..

provalo nell'applicazione Windows Form, potresti ricevere un errore

"Si è verificata un'eccezione non gestita di tipo" System.StackOverflowException "in System.Windows.Forms.dll"


Dalla domanda: "Ho appena aggiunto un TextBox e un TextBlock al modello di interfaccia utente standard" - non sono la stessa cosa. Ho un TextBox in cui l'utente può digitare e un TextBlock che mostra il conteggio.
Jon Skeet

0

StefanWick ha ragione, considera l'utilizzo di questo modello

<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

È un vecchio argomento, ma invece di cambiare modello (che non funziona per me, non vedo l'altra casella di testo con Blend) puoi aggiungere booleano per verificare se l'evento ha già svolto la funzione o meno.

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

Sono consapevole che NON è il modo perfetto, ma penso che sia il modo più semplice per farlo. E funziona.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.