El evento TextBox.TextChanged se activa dos veces en el emulador de Windows Phone 7

91

Tengo una aplicación de prueba muy simple para jugar con Windows Phone 7. Acabo de agregar un TextBoxy un TextBlocka la plantilla de interfaz de usuario estándar. El único código personalizado es el siguiente:

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

El TextBox.TextChangedevento está conectado a TextBoxChangedXAML:

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

Sin embargo, cada vez que presiono una tecla cuando se ejecuta en el emulador (ya sea el teclado en pantalla o el físico, después de presionar Pausa para habilitar este último), aumenta el contador dos veces, mostrando dos líneas en el TextBlock. Todo lo que he intentado muestra que el evento realmente se está disparando dos veces y no tengo idea de por qué. He verificado que solo se suscribe una vez: si me doy de baja en el MainPageconstructor, no pasa nada (en el bloque de texto) cuando el texto cambia.

Probé el código equivalente en una aplicación Silverlight normal y no ocurrió allí. No tengo un teléfono físico para reproducir esto en este momento. No he encontrado ningún registro de que esto sea un problema conocido en Windows Phone 7.

¿Alguien puede explicar qué estoy haciendo mal, o debería informar de esto como un error?

EDITAR: Para reducir la posibilidad de que esto se deba a tener dos controles de texto, he intentado eliminar por TextBlockcompleto y cambiar el método TextBoxChanged para solo incrementar counter. Luego ejecuté el emulador, escribí 10 letras y luego puse un punto de interrupción en la counter++;línea (solo para deshacerme de cualquier posibilidad de que irrumpir en el depurador esté causando problemas), y se muestra countercomo 20.

EDITAR: Ahora he preguntado en el foro de Windows Phone 7 ... veremos qué sucede.

Jon Skeet
fuente
Solo por interés: si revisa el interior del evento, ¿el contenido del TextBox es el mismo las dos veces que se activa el evento? Realmente no sé por qué sucedería esto, ya que generalmente uso MVVM y enlace de datos en lugar de manejo de eventos para estas cosas (Silverlight y WPF, no mucha experiencia con WP7).
Rune Jacobsen
@Rune: Sí, veo el texto "después" dos veces. Entonces, si textBox1.Textpresiono "h" y lo visualizo como parte de la adición textBlock1, mostrará "h" en ambas líneas.
Jon Skeet
1
Mencionas los 2 teclados, ¿podría ser un factor? ¿Puedes desactivar uno? ¿Y tal vez pueda verificar si todos los miembros de TextChangedEventArgs son iguales en ambas llamadas?
Henk Holterman
@Henk: La mayoría de las veces no me he molestado en habilitar el teclado físico ... solo para ver si eso tiene algún efecto. TextChangedEventArgsrealmente no tiene mucho disponible, solo el OriginalSource, que siempre es nulo.
Jon Skeet
3
Parece un error, no está relacionado con el teclado porque puede obtener los mismos resultados simplemente asignando un nuevo valor a la propiedad Text, TextChanged aún se dispara dos veces.
AnthonyWJones

Respuestas:

75

La razón por la TextChangedque el evento se dispara dos veces en WP7 es un efecto secundario de cómo TextBoxse ha diseñado para el aspecto de Metro.

Si edita la TextBoxplantilla en Blend, verá que contiene una secundaria TextBoxpara el estado deshabilitado / solo lectura. Esto provoca, como efecto secundario, que el evento se dispare dos veces.

Puede cambiar la plantilla para eliminar el extra TextBox(y los estados asociados) si no necesita estos estados, o modificar la plantilla para lograr un aspecto diferente en el estado deshabilitado / de solo lectura, sin usar un secundario TextBox.

Con eso, el evento se disparará solo una vez.

Stefan Wick MSFT
fuente
18

Yo iría por el error, principalmente porque si pones los eventos KeyDowny KeyUpallí, muestra que se activan solo una vez (cada uno de ellos) pero el TextBoxChangedevento se activa dos veces.

enterrador
fuente
@undertakeror: Gracias por revisar esa parte. Haré la misma pregunta en el foro específico de WP7 y veré cuál es la respuesta ...
Jon Skeet
¿Qué hace TextInput? Parece un error bastante grande pasar por las pruebas unitarias del WP7, pero luego es SL
Chris S
@Chris S: ¿Qué quieres decir con "¿Qué hace TextInput?" No estoy familiarizado con TextInput...
Jon Skeet
@Jon `OnTextInput (TextCompositionEventArgs e)` es la forma de SL de manejar la entrada de texto en lugar de KeyDown, ya que obviamente el dispositivo puede no tener un teclado: "Ocurre cuando un elemento de la interfaz de usuario obtiene texto de una manera independiente del dispositivo" msdn.microsoft. com / en-us / library /…
Chris S
Solo tenía curiosidad por saber si eso se disparó dos veces también
Chris S
8

Eso me suena como un error. Como solución alternativa, siempre puede usar Rx DistinctUntilChanged. Hay una sobrecarga que le permite especificar la clave distinta.

Este método de extensión devuelve el evento TextChanged observable pero omite duplicados consecutivos:

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 vez que se corrige el error, simplemente puede eliminar la DistinctUntilChangedlínea.

Richard Szalay
fuente
2

¡Agradable! Encontré esta pregunta al buscar un problema relacionado y también encontré esta cosa molesta en mi código. En mi caso, el evento doble consume más recursos de CPU. Entonces, arreglé mi cuadro de texto de filtro en tiempo real con esta solución:

private string filterText = String.Empty;

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

}
crea7or
fuente
1

Creo que esto siempre ha sido un error en Compact Framework. Debe haber sido transferido a WP7.

Jerod Houghtelling
fuente
Pensé que estaba arreglado en una versión más reciente del CF ... y sería extraño entrar a pesar del cambio a Silverlight. Por otro lado, es un error bastante extraño de ver de todos modos ...
Jon Skeet
Estoy de acuerdo en que es extraño. Ayer volví a utilizarlo en una aplicación CF 2.0.
Jerod Houghtelling
0

Claro que me parece un error, si está tratando de generar un evento cada vez que el texto cambia, podría intentar usar un enlace bidireccional en su lugar, desafortunadamente esto no generará eventos de cambio por pulsación de tecla (solo cuando el campo pierde el enfoque). Aquí hay una solución alternativa si la necesita:

        this.textBox1.TextChanged -= this.TextBoxChanged;
        textBlock1.Text += "Text changed " + (counter++) + "\r\n";
        this.textBox1.TextChanged += this.TextBoxChanged;
Flatliner DOA
fuente
No estoy seguro de que eso funcione, el problema no es que el controlador de eventos se active debido al textBlock1.Textcambio, aunque lo intentaré. (La solución que iba a intentar era hacer que mi manejador de eventos tuviera estado, recordando el texto anterior. Si no ha cambiado, ignóralo :)
Jon Skeet
0

Descargo de responsabilidad: no estoy familiarizado con los matices de xaml y sé que esto suena ilógico ... pero de todos modos, mi primer pensamiento es intentar pasar simplemente como eventos simples en lugar de cambios de texto. No tiene sentido, pero ¿podría ayudar? Parece que cuando he visto disparos dobles como este antes, se debe a un error o debido a que de alguna manera 2 agregan llamadas al controlador de eventos que ocurren detrás de escena ... ¿No estoy seguro de cuál?

Si necesita rápido y sucio, nuevamente, no tengo experiencia con xaml, mi siguiente paso sería simplemente omitir xaml para ese cuadro de texto como una solución rápida ... haga ese cuadro de texto totalmente en c # por ahora hasta que pueda identificar el error o código complicado ... es decir, si necesita una solución temporal.

Pimp Juice McJones
fuente
No soy yo quien pasa ningún argumento de evento, estoy implementando un controlador de eventos. Pero he verificado que agregar el controlador de eventos exclusivamente en C # no hace ninguna diferencia ... todavía se activa dos veces.
Jon Skeet
Está bien, hmmm. Sí, si es puro c # entonces suena más como un error. Acerca de la primera sugerencia: lo siento, mi verbage fue horrible, lo que debería haber dicho es: intentaría [en su implementación / método de controlador TextBoxChanged] cambiar el tipo de parámetro args a simplemente eventargs. Probablemente no funcione ... pero bueno ... fue solo mi primer pensamiento.
Pimp Juice McJones
En otras palabras, probablemente no funcionará, pero probaría el método signature = private void TextBoxChanged (remitente del objeto, EventArgs e) solo para decir que lo probé =)
Pimp Juice McJones
Correcto. No creo que eso tenga ningún efecto, me temo.
Jon Skeet
0

No creo que sea un error ... Cuando asigna el valor a una propiedad de texto dentro del evento textchanged, el valor del cuadro de texto se cambia, lo que volverá a llamar al evento de cambio de texto ...

intente esto en la aplicación Windows Forms, es posible que obtenga un error

"Se produjo una excepción no controlada del tipo 'System.StackOverflowException' en System.Windows.Forms.dll"

Senthil Kumar B
fuente
De la pregunta: "Acabo de agregar un TextBox y un TextBlock a la plantilla de IU estándar", no son lo mismo. Tengo un TextBox en el que el usuario puede escribir y un TextBlock que muestra el recuento.
Jon Skeet
0

StefanWick tiene razón, considere usar esta plantilla

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

Es un tema antiguo, pero en lugar de cambiar la plantilla (eso no funciona para mí, no veo el otro cuadro de texto con Blend) puede agregar boolean para verificar si el evento ya hizo la función o no.

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

Soy consciente de que NO es la forma perfecta, pero creo que es la forma más sencilla de hacerlo. Y funciona.

TDK
fuente