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" />

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

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

Хто-небудь може пояснити, що я роблю неправильно, чи я повинен повідомити про це як про помилку?

РЕДАГУВАТИ: Щоб зменшити можливість цього бути лише двома елементами керування текстом, я спробував видалити 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
Ви згадуєте 2 клавіатури, чи може це бути фактором? Чи можете ви відключити один? І можливо, ви можете перевірити, чи всі члени TextChangedEventArgs рівні в обох викликах?
Henk Holterman

@Henk: Здебільшого я не турбувався про те, щоб увімкнути фізичну клавіатуру ... лише для того, щоб перевірити, чи це матиме ефект. TextChangedEventArgsнасправді не має багато доступного - лише те OriginalSource, що завжди є нульовим.
Джон Скіт,

3
Це схоже на помилку, це не пов'язано з клавіатурою, тому що ви можете отримати однакові результати, просто призначивши нове значення властивості Text, TextChanged все одно спрацьовує двічі.
AnthonyWJones

Відповіді:


75

Причиною того, що TextChangedподія спрацьовує двічі в WP7, є побічний ефект від того TextBox, як шаблон був оформлений для вигляду метро.

Якщо ви відредагуєте TextBoxшаблон у Blend, ви побачите, що він містить вторинну інформацію TextBoxдля стану відключеного / лише для читання. Це спричиняє побічний ефект, що подія запускається двічі.

Ви можете змінити шаблон, щоб видалити зайві TextBox(і пов’язані з ними стани), якщо ці стани вам не потрібні, або змінити шаблон, щоб досягти іншого вигляду у стані відключеного / лише для читання, без використання вторинного TextBox.

При цьому подія запускатиметься лише один раз.


18

я б пішов на помилку, головним чином тому, що якщо ви помістите туди події KeyDownта KeyUpподії, це показує, що вони запускаються лише один раз (кожен з них), але TextBoxChangedподія запускається двічі


@undertakeror: Дякуємо за перевірку цього біта. Я задам те саме питання на форумі, присвяченому WP7, і подивлюся, яка відповідь ...
Джон Скіт,

Що робить TextInput? Це здається досить великою помилкою, щоб проскочити модульні тести WP7, але тоді це SL
Chris S

@Chris S: Що ви маєте на увазі під словом "Що робить TextInput?" Я не знайомий з TextInput...
Джон Скіт,

@Jon `OnTextInput (TextCompositionEventArgs e)` - це SL-спосіб обробки введення тексту замість KeyDown, оскільки, очевидно, пристрій може не мати клавіатури: "Виникає, коли елемент інтерфейсу отримує текст незалежно від пристрою" msdn.microsoft. com / en-us / library /…
Chris S

Мені було просто цікаво, чи це вистрілить удвічі краще
Chris S

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

}

1

Я вважаю, що це завжди було помилкою в Compact Framework. Це, мабуть, було перенесено в WP7.


Я думав, що це виправлено в останній версії CF ... і було б дивно потрапити, незважаючи на перехід до Silverlight. З іншого боку, це досить дивна помилка, яку все одно можна побачити ...
Jon Skeet

Я згоден, що це дивно. Вчора я пересвідчився в додатку CF 2.0.
Jerod Houghtelling

0

Звичайно, це здається мені помилкою, якщо ви намагаєтеся викликати подію кожного разу, коли текст змінюється, ви можете спробувати скористатися двостороннім прив'язуванням, на жаль, це не призведе до подій зміни натискання на клавішу (лише коли поле втрачає фокус). Ось обхідний спосіб, якщо він вам потрібен:

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

Я не впевнений, що це обійде ситуацію - проблема не в тому, що обробник подій спрацьовує через textBlock1.Textзміну - я все ж спробую. ( Обхідним шляхом, який я збирався спробувати, було зробити мого організатора подій державним, пам’ятаючи попередній текст. Якщо він насправді не змінився, ігноруйте його :)
Джон Скіт,

0

Застереження - я не знайомий з нюансами xaml, і я знаю, що це звучить нелогічно ... але в будь-якому випадку - моя перша думка полягає в тому, щоб спробувати пройти як звичайні парні цілі, а не як текстові шрифти. Не має сенсу, але може бути, це може допомогти? Здається, коли я вже бачив подібні подвійні стрільби, що це або через помилку, або через те, що якимось чином додано 2 виклики обробника подій, що відбуваються за лаштунками ... Я не впевнений, які?

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


Я не передаю будь-які аргументи подій - я реалізую обробник подій. Але я переконався, що додавання обробника подій суто в C # не має ніякої різниці ... він все одно звільняється двічі.
Джон Скіт,

Гаразд, ммм. Так, якщо це чистий c #, то це більше схоже на помилку. Приблизно за першою пропозицією - Мені шкода, що мій verbage був жахливим, як я повинен був сказати - я спробував би [у вашій реалізації / метод обробника TextBoxChanged] змінити тип параметра args на звичайний eventargs. Можливо, це не спрацює ... але ей ... це була лише моя перша думка.
Pimp Juice McJones

Іншими словами, це, мабуть, не спрацює, але я спробую метод signature = private void TextBoxChanged (відправник об’єкта, EventArgs e), щоб сказати, що я спробував =)
Pimp Juice McJones

Правильно. Я не думаю, що це матиме ефект, я боюся.
Джон Скіт,

0

Я не думаю, що це помилка .. Коли ви призначаєте значення властивості тексту всередині події, зміненої текстом, значення текстового поля змінюється, що знову викликатиме зміну тексту.

спробуйте це в додатку Windows Forms, можливо, ви отримаєте помилку

"У System.Windows.Forms.dll стався необроблений виняток типу 'System.StackOverflowException'"


Із запитання: "Я щойно додав TextBox і TextBlock до стандартного шаблону інтерфейсу" - це не одне і те ж. У мене є один 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>

0

Це стара тема, але замість зміни шаблону (це не працює у мене, я не бачу іншого текстового поля з Blend) ви можете додати логічне значення, щоб перевірити, чи подія вже виконувала цю функцію чи ні.

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

Я усвідомлюю, що це НЕ ідеальний спосіб, але я думаю, що це простий спосіб це зробити. І це працює.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.