Событие TextBox.TextChanged срабатывает дважды в эмуляторе Windows Phone 7

91

У меня есть очень простое тестовое приложение, чтобы поиграть с Windows Phone 7. Я только что добавил a TextBoxи a TextBlockк стандартному шаблону пользовательского интерфейса. Единственный настраиваемый код следующий:

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

TextBox.TextChangedСобытие подключается к TextBoxChangedв XAML:

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

Однако каждый раз, когда я нажимаю клавишу во время работы в эмуляторе (на экранной клавиатуре или на физической, нажав Pause для включения последней), он увеличивает счетчик дважды, отображая две строки в файле TextBlock. Все, что я пробовал, показывает, что мероприятие действительно запускается дважды, и я не знаю почему. Я подтвердил, что он подписывается только один раз - если я откажусь от подписки в MainPageконструкторе, вообще ничего не произойдет (с текстовым блоком) при изменении текста.

Я пробовал эквивалентный код в обычном приложении Silverlight, но там этого не произошло. На данный момент у меня нет физического телефона, чтобы воспроизвести это. Я не нашел никаких свидетельств того, что это известная проблема в Windows Phone 7.

Может ли кто-нибудь объяснить, что я делаю не так, или мне следует сообщить об этом как об ошибке?

EDIT: чтобы уменьшить вероятность того, что это TextBlockсвязано с двумя текстовыми элементами управления, я попытался полностью удалить и изменить метод TextBoxChanged на просто увеличение counter. Затем я запустил эмулятор, набрал 10 букв, а затем поставил точку останова на counter++;строке (просто чтобы избавиться от любой возможности того, что взлом отладчика вызывает проблемы) - и отображается counterкак 20.

EDIT: я спросил на форуме Windows Phone 7 ... посмотрим, что произойдет.

Джон Скит
источник
Просто из интереса - если вы проверяете внутри события, является ли содержимое TextBox одинаковым при обоих срабатываниях события? Я действительно не знаю, почему это могло произойти, поскольку я обычно использую MVVM и привязку данных вместо обработки событий для этих вещей (Silverlight и WPF, небольшой опыт работы с WP7).
Rune Jacobsen
@Rune: Да, я дважды вижу текст «после». Поэтому, если я textBox1.Textнажму «h» и отобразлю как часть добавления textBlock1, в обеих строках будет отображаться «h».
Джон Скит,
1
Вы упомянули две клавиатуры, может ли это быть фактором? Вы можете отключить один? И, может быть, вы сможете проверить, все ли члены TextChangedEventArgs равны в обоих вызовах?
Хенк Холтерман,
@Henk: Большую часть времени я не беспокоился о включении физической клавиатуры ... только для того, чтобы посмотреть, повлияет ли это. TextChangedEventArgsна самом деле не так много доступно - просто OriginalSource, который всегда имеет значение null.
Джон Скит,
3
Это действительно похоже на ошибку, это не связано с клавиатурой, потому что вы можете получить те же результаты, просто назначив новое значение свойству Text, TextChanged по-прежнему срабатывает дважды.
AnthonyWJones

Ответы:

75

Причина, по которой TextChangedсобытие запускается дважды в WP7, является побочным эффектом того, как TextBoxшаблон был создан для внешнего вида Metro.

Если вы отредактируете TextBoxшаблон в Blend, вы увидите, что он содержит вторичный объект TextBoxдля состояния отключено / только для чтения. Это приводит к тому, что событие запускается дважды.

Вы можете изменить шаблон, чтобы удалить лишние TextBox(и связанные состояния), если вам не нужны эти состояния, или изменить шаблон, чтобы добиться другого вида в отключенном состоянии / состоянии только для чтения, без использования вторичного TextBox.

При этом событие сработает только один раз.

Стефан Вик MSFT
источник
18

я бы пойти на ошибку, в основном потому , что если вы положили KeyDownи KeyUpсобытия там, это показывает , что они обжигают только один раз (каждый из них) , но TextBoxChangedсобытие вызывается дважды

гробовщик
источник
@undertakeror: Спасибо, что ознакомились с этой частью. Я задам тот же вопрос на форуме, посвященном WP7, и посмотрю, какой будет ответ ...
Джон Скит,
Что делает TextInput? Это кажется довольно серьезной ошибкой, чтобы проскользнуть через модульные тесты WP7, но тогда это SL
Крис С.
@Chris S: Что вы имеете в виду, говоря "Что делает TextInput?" Я не знаком с TextInput...
Джон Скит
@Jon `OnTextInput (TextCompositionEventArgs e)` - это способ SL обработки ввода текста вместо KeyDown, поскольку, очевидно, устройство может не иметь клавиатуры: «Происходит, когда элемент пользовательского интерфейса получает текст независимо от устройства» msdn.microsoft. com / en-us / library /…
Крис С.
Мне было просто любопытно, сработало ли это дважды
Крис С.
8

Для меня это звучит как ошибка. В качестве обходного пути вы всегда можете использовать Rx DistinctUntilChanged. Существует перегрузка, позволяющая указать отдельный ключ.

Этот метод расширения возвращает наблюдаемое событие TextChanged, но пропускает последовательные дубликаты:

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

Как только ошибка будет исправлена, вы можете просто удалить DistinctUntilChangedстроку.

Ричард Салай
источник
2

Ницца! Я нашел этот вопрос, выполнив поиск связанной проблемы, а также нашел эту раздражающую вещь в своем коде. В моем случае двойное событие съедает больше ресурсов процессора. Итак, я исправил текстовое поле фильтра в реальном времени с помощью этого решения:

private string filterText = String.Empty;

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

}
Crea7or
источник
1

Я считаю, что это всегда было ошибкой в ​​Compact Framework. Должно быть, он был перенесен в WP7.

Джерод Хоутеллинг
источник
Я думал, что это было исправлено в более поздней версии CF ... и было бы странно войти, несмотря на переход на Silverlight. С другой стороны, в любом случае это довольно странная ошибка ...
Джон Скит,
Я согласен, что это странно. Вчера я повторил это в приложении CF 2.0.
Джерод Хоутеллинг,
0

Конечно, мне кажется, что это ошибка, если вы пытаетесь инициировать событие каждый раз, когда изменяется текст, вы можете попробовать вместо этого использовать двустороннюю привязку, к сожалению, это не вызовет событий изменения нажатия каждой клавиши (только когда поле теряет фокус). Вот обходной путь, если он вам нужен:

        this.textBox1.TextChanged -= this.TextBoxChanged;
        textBlock1.Text += "Text changed " + (counter++) + "\r\n";
        this.textBox1.TextChanged += this.TextBoxChanged;
Flatliner DOA
источник
Я не уверен, что это сработает - проблема не в том, что обработчик событий запускается из-за textBlock1.Textизменений - я все же попробую. (Обходной путь, который я собирался попробовать, состоял в том, чтобы сделать мой обработчик событий отслеживающим состояние, запомнив предыдущий текст. Если он на самом деле не изменился, игнорируйте его :)
Джон Скит,
0

Отказ от ответственности - я не знаком с нюансами xaml и знаю, что это звучит нелогично ... но в любом случае - моя первая мысль - попытаться передать как простые аргументы событий, а не textchangedeventargs. Не имеет смысла, но, может быть, это поможет? Похоже, что когда я раньше видел подобные двойные срабатывания, это либо из-за ошибки, либо из-за каких-то двух вызовов обработчиков событий добавления, происходящих за кулисами ... Я не уверен, что именно?

Если вам нужно быстро и грязно, опять же, у меня нет опыта работы с xaml - моим следующим шагом было бы просто пропустить xaml для этого текстового поля в качестве быстрого обходного пути ... сделайте это текстовое поле полностью на C #, пока вы не сможете точно определить ошибку или хитрый код ... то есть, если вам нужно временное решение.

Сутенер Джус МакДжонс
источник
Я не тот, кто передает какие-либо аргументы событий - я реализую обработчик событий. Но я убедился, что добавление обработчика событий исключительно на C # не имеет значения ... он все равно запускается дважды.
Джон Скит,
Хорошо, хммм. Да, если это чистый C #, то это больше похоже на ошибку. По поводу первого предложения - мне очень жаль, что мой текст был ужасен, как я должен был сказать - я бы попробовал [в вашей реализации / методе обработчика TextBoxChanged] изменить тип параметра args на простые аргументы событий. Наверное, не сработает ... но эй ... это была моя первая мысль.
Pimp Juice McJones
Другими словами, это, вероятно, не сработает, но я бы попробовал подпись метода = private void TextBoxChanged (отправитель объекта, EventArgs e), просто чтобы сказать, что я это пробовал =)
Pimp Juice McJones
Правильно. Боюсь, что не думаю, что это будет эффективно.
Джон Скит,
0

Я не думаю, что это ошибка .. Когда вы присваиваете значение свойству text внутри события textchanged, значение текстового поля изменяется, что снова вызывает событие изменения текста ..

попробуйте это в приложении Windows Forms, вы можете получить сообщение об ошибке

«Необработанное исключение типа 'System.StackOverflowException' произошло в System.Windows.Forms.dll»

Сентил Кумар Б
источник
Из вопроса: «Я только что добавил TextBox и TextBlock в стандартный шаблон пользовательского интерфейса» - это не одно и то же. У меня есть одно текстовое поле, в которое пользователь может вводить текст, и один текстовый блок, который отображает счетчик.
Джон Скит
0

StefanWick прав, рассмотрите возможность использования этого шаблона

<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
источник
0

Это старая тема, но вместо шаблона изменения (который не работает для меня, я не вижу другого текстового поля с Blend) вы можете добавить логическое значение, чтобы проверить, выполнило ли событие уже эту функцию или нет.

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

Я понимаю, что это НЕ идеальный способ, но я думаю, что это самый простой способ сделать это. И это работает.

TDK
источник