Evento TextBox.TextChanged disparado duas vezes no emulador do Windows Phone 7

91

Tenho um aplicativo de teste muito simples para brincar com o Windows Phone 7. Acabei de adicionar um TextBoxe um TextBlockao modelo de IU padrão. O único código personalizado é o seguinte:

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

O TextBox.TextChangedevento está conectado ao TextBoxChangedXAML:

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

Porém, toda vez que pressiono uma tecla durante a execução do emulador (seja o teclado na tela ou o físico, tendo pressionado Pause para habilitar o último), ele incrementa o contador duas vezes, exibindo duas linhas no TextBlock. Tudo o que experimentei mostra que o evento está realmente disparando duas vezes, e não tenho ideia do motivo. Eu verifiquei que ele só está sendo inscrito uma vez - se eu cancelar a inscrição no MainPageconstrutor, nada acontece (ao bloco de texto) quando o texto muda.

Tentei o código equivalente em um aplicativo Silverlight regular e não ocorreu lá. Não tenho um telefone físico para reproduzir isso no momento. Não encontrei nenhum registro deste ser um problema conhecido no Windows Phone 7.

Alguém pode explicar o que estou fazendo de errado ou devo relatar isso como um bug?

EDIT: Para reduzir a possibilidade de isso ser reduzido a dois controles de texto, tentei remover o TextBlockcompletamente e alterar o método TextBoxChanged para apenas incrementar counter. Eu, em seguida, executar no emulador, digitado 10 letras e , em seguida, colocar um ponto de interrupção nacounter++; linha (apenas para se livrar de qualquer possibilidade de invadir o depurador está causando problemas) - e mostra countercomo 20.

EDIT: Eu já perguntei no fórum do Windows Phone 7 ... vamos ver o que acontece.

Jon Skeet
fonte
Só por curiosidade - se você verificar dentro do evento, o conteúdo da TextBox é o mesmo nas duas vezes em que o evento é disparado? Eu realmente não sei por que isso aconteceria, já que geralmente uso MVVM e vinculação de dados em vez de manipulação de eventos para essas coisas (Silverlight e WPF, não tenho muita experiência com WP7).
Rune Jacobsen
@Rune: Sim, vejo o texto "depois" duas vezes. Portanto, se eu pressionar "h" e for exibido textBox1.Textcomo parte da adição textBlock1, ele mostrará "h" em ambas as linhas.
Jon Skeet
1
Você mencionou os 2 teclados, isso poderia ser um fator? Você pode desabilitar um? E talvez você possa verificar se todos os membros de TextChangedEventArgs são iguais em ambas as chamadas?
Henk Holterman
@Henk: Na maioria das vezes não me incomodei em habilitar o teclado físico ... só para ver se isso surtiria algum efeito. TextChangedEventArgsnão tem muito disponível - apenas o OriginalSource, que é sempre nulo.
Jon Skeet
3
Parece um bug, não está relacionado ao teclado porque você pode obter os mesmos resultados simplesmente atribuindo um novo valor à propriedade Text, o TextChanged ainda dispara duas vezes.
AnthonyWJones

Respostas:

75

O motivo do TextChangedevento ser disparado duas vezes no WP7 é um efeito colateral de como oTextBox foi projetado para a aparência do Metro.

Se você editar o TextBoxmodelo no Blend, verá que ele contém umTextBox para o estado desabilitado / somente leitura. Isso faz com que, como efeito colateral, o evento seja disparado duas vezes.

Você pode alterar o modelo para remover os TextBoxestados extras (e associados) se não precisar desses estados, ou modificar o modelo para obter uma aparência diferente no estado desativado / somente leitura, sem usar um secundárioTextBox .

Com isso, o evento será disparado apenas uma vez.

Stefan Wick MSFT
fonte
18

Eu iria para o bug, principalmente porque se você colocar os eventos KeyDowne KeyUplá, isso mostra que eles são disparados apenas uma vez (cada um deles), mas o TextBoxChangedevento é disparado duas vezes

empreendedor
fonte
@undertakeror: Obrigado por verificar isso. Vou fazer a mesma pergunta no fórum específico do WP7 e ver qual é a resposta ...
Jon Skeet
O que TextInput faz? Este parece ser um grande bug para escapar dos testes de unidade do WP7, mas então é SL
Chris S
@Chris S: O que você quer dizer com "O que TextInputfaz?" Não estou familiarizado com TextInput...
Jon Skeet
@Jon `OnTextInput (TextCompositionEventArgs e)` é a maneira SL de lidar com a entrada de texto em vez do KeyDown, já que obviamente o dispositivo pode não ter um teclado: "Ocorre quando um elemento da IU obtém texto de maneira independente do dispositivo" msdn.microsoft. com / en-us / library /…
Chris S
Eu estava curioso para saber se disparou duas vezes também
Chris S
8

Isso soa como um bug para mim. Como solução alternativa, você sempre pode usar Rx DistinctUntilChanged. Existe uma sobrecarga que permite especificar a chave distinta.

Este método de extensão retorna o evento TextChanged observável, mas ignora duplicatas consecutivas:

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

Assim que o bug for corrigido, você pode simplesmente remover a DistinctUntilChangedlinha.

Richard Szalay
fonte
2

Agradável! Eu encontrei essa pergunta procurando por um problema relacionado e também achei essa coisa irritante no meu código. O evento duplo consome mais recursos da CPU no meu caso. Então, eu consertei minha caixa de texto de filtro em tempo real com esta solução:

private string filterText = String.Empty;

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

}
crea7or
fonte
1

Acredito que isso sempre foi um bug no Compact Framework. Deve ter sido transportado para o WP7.

Jerod Houghtelling
fonte
Achei que tivesse sido corrigido em uma versão mais recente do CF ... e seria estranho entrar, apesar da mudança para o Silverlight. Por outro lado, é um bug muito estranho de se ver ...
Jon Skeet
Eu concordo que é estranho. Eu voltei a fazê-lo ontem em um aplicativo CF 2.0.
Jerod Houghtelling
0

Claro que parece um bug para mim, se você está tentando acionar um evento toda vez que o texto muda, você pode tentar usar uma ligação bidirecional, infelizmente isso não levantará eventos de mudança por pressionamento de tecla (somente quando o campo perde o foco). Aqui está uma solução alternativa se você precisar de uma:

        this.textBox1.TextChanged -= this.TextBoxChanged;
        textBlock1.Text += "Text changed " + (counter++) + "\r\n";
        this.textBox1.TextChanged += this.TextBoxChanged;
Flatliner DOA
fonte
Não tenho certeza se isso vai resolver isso - o problema não é o acionador do manipulador de eventos por causa da textBlock1.Textmudança - vou tentar. (A solução que eu tentaria era tornar meu manipulador de eventos com estado, lembrando o texto anterior. Se ele não mudou, ignore :)
Jon Skeet
0

Disclaimer- Não estou familiarizado com nuances xaml e sei que isso soa ilógico ... mas de qualquer maneira- meu primeiro pensamento é tentar passar apenas como eventos simples, em vez de eventos modificados por texto. Não faz sentido, mas pode ajudar? Parece que quando eu vi disparos duplos como este antes que é devido a um bug ou devido a alguma forma de 2 chamadas de manipulador de eventos adicionais acontecendo nos bastidores ... Não tenho certeza de qual?

Se você precisa de algo rápido e sujo, novamente, eu não tendo experiência com xaml - meu próximo passo seria apenas pular o xaml para aquela caixa de texto como uma solução rápida ... faça essa caixa de texto totalmente em c # por enquanto até que você possa localizar o bug ou código complicado ... isto é, se você precisar de uma solução temporária.

Pimp Juice McJones
fonte
Não sou eu que estou passando nenhum argumento de evento - estou implementando um manipulador de eventos. Mas eu verifiquei que adicionar o manipulador de eventos puramente em C # não faz diferença ... ele ainda é acionado duas vezes.
Jon Skeet
OK, hmmm. Sim, se for puro c #, soa mais como um bug. Sobre pela primeira sugestão - sinto muito, meu verbage foi horrível, como eu deveria ter declarado é - eu tentaria [em sua implementação / método do manipulador TextBoxChanged] alterar o tipo de parâmetro args para apenas eventargs simples. Provavelmente não vai funcionar ... mas hey ... foi apenas o meu primeiro pensamento.
Pimp Juice McJones
Em outras palavras, provavelmente não funcionará, mas eu tentaria o método signature = private void TextBoxChanged (object sender, EventArgs e) apenas para dizer que tentei =)
Pimp Juice McJones
Certo. Acho que não terá nenhum efeito, temo.
Jon Skeet
0

Não acho que seja um bug. Quando você atribui o valor a uma propriedade text dentro do evento textchanged, o valor da caixa de texto é alterado, o que irá chamar novamente o evento text alterado.

tente fazer isso no aplicativo Windows Forms, você pode obter um erro

"Ocorreu uma exceção não tratada do tipo 'System.StackOverflowException' em System.Windows.Forms.dll"

Senthil Kumar B
fonte
Da pergunta: "Acabei de adicionar um TextBox e um TextBlock ao modelo de IU padrão" - eles não são a mesma coisa. Eu tenho um TextBox no qual o usuário pode digitar e um TextBlock que exibe a contagem.
Jon Skeet
0

StefanWick está certo, considere usar este modelo

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

É um tópico antigo, mas ao invés de mudar o template (isso não funciona para mim, não vejo a outra caixa de texto com o Blend) você pode adicionar booleano para verificar se o evento já fez a função ou não.

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

Sei que NÃO é a maneira perfeita, mas acho que é a maneira mais simples de fazer isso. E funciona.

TDK
fonte