Peristiwa TextBox.TextChanged yang diaktifkan dua kali pada emulator Windows Phone 7

91

Saya memiliki aplikasi pengujian yang sangat sederhana hanya untuk bermain-main dengan Windows Phone 7. Saya baru saja menambahkan a TextBoxdan a TextBlockke template UI standar. Satu-satunya kode kustom adalah sebagai berikut:

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

The TextBox.TextChangedevent adalah kabel sampai TextBoxChangeddi XAML:

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

Namun, setiap kali saya menekan tombol saat menjalankan emulator (baik keyboard di layar atau keyboard fisik, setelah menekan Jeda untuk mengaktifkan yang terakhir), penghitung akan bertambah dua kali, menampilkan dua baris di TextBlock. Semua yang saya coba menunjukkan bahwa acara tersebut benar-benar terjadi dua kali, dan saya tidak tahu mengapa. Saya telah memverifikasi bahwa itu hanya berlangganan sekali - jika saya berhenti berlangganan di MainPagekonstruktor, tidak ada yang terjadi sama sekali (ke blok teks) ketika teks berubah.

Saya sudah mencoba kode yang setara di aplikasi Silverlight biasa, dan itu tidak terjadi di sana. Saat ini saya tidak memiliki ponsel fisik untuk mereproduksi ini. Saya belum menemukan catatan apa pun tentang ini sebagai masalah yang diketahui di Windows Phone 7.

Adakah yang bisa menjelaskan kesalahan saya, atau haruskah saya melaporkan ini sebagai bug?

EDIT: Untuk mengurangi kemungkinan ini menjadi memiliki dua kontrol teks, saya telah mencoba menghapus TextBlocksepenuhnya, dan mengubah metode TextBoxChanged menjadi hanya kenaikan counter. Saya kemudian menjalankan emulator, mengetik 10 huruf dan kemudian meletakkan breakpoint pada counter++;baris (hanya untuk menghilangkan kemungkinan bahwa membobol debugger menyebabkan masalah) - dan itu ditampilkan countersebagai 20.

EDIT: Saya sekarang sudah bertanya di forum Windows Phone 7 ... kita akan lihat apa yang terjadi.

Jon Skeet
sumber
Hanya karena minat - jika Anda memeriksa di dalam acara, apakah konten TextBox sama di kedua kali acara tersebut diaktifkan? Saya tidak begitu tahu mengapa ini terjadi, karena saya biasanya menggunakan MVVM dan pengikatan data daripada penanganan acara untuk hal-hal ini (Silverlight dan WPF, tidak banyak pengalaman dengan WP7).
Rune Jacobsen
@ Rune: Ya, saya melihat teks "setelah" dua kali. Jadi jika saya menekan "h" dan menampilkan textBox1.Textsebagai bagian dari penambahan textBlock1, itu akan menunjukkan "h" di kedua baris.
Jon Skeet
1
Anda menyebutkan 2 keyboard, mungkinkah itu faktornya? Bisakah Anda menonaktifkannya? Dan mungkin Anda dapat memeriksa apakah semua anggota TextChangedEventArgs sama di kedua panggilan?
Henk Holterman
@Henk: Sebagian besar waktu saya tidak repot-repot mengaktifkan keyboard fisik ... hanya untuk melihat apakah itu akan berpengaruh. TextChangedEventArgstidak banyak yang tersedia - hanya OriginalSource, yang selalu nol.
Jon Skeet
3
Ini memang terlihat seperti bug, ini tidak terkait dengan keyboard karena Anda bisa mendapatkan hasil yang sama hanya dengan menetapkan nilai baru ke properti Text, TextChanged masih aktif dua kali.
AnthonyWJones

Jawaban:

75

Alasan TextChangedperistiwa tersebut terjadi dua kali di WP7 adalah efek samping dari bagaimana TextBoxtemplate telah dibuat untuk tampilan Metro.

Jika Anda mengedit TextBoxtemplate di Blend, Anda akan melihat bahwa template tersebut berisi template sekunder TextBoxuntuk status nonaktif / read-only. Hal ini menyebabkan, sebagai efek samping, peristiwa tersebut diaktifkan dua kali.

Anda dapat mengubah template untuk menghapus status tambahan TextBox(dan status terkait) jika Anda tidak memerlukan status ini, atau memodifikasi template untuk mendapatkan tampilan yang berbeda dalam status nonaktif / hanya baca, tanpa menggunakan status sekunder TextBox.

Dengan begitu, acara hanya akan aktif sekali.

Stefan Wick MSFT
sumber
18

saya akan mencari bug, terutama karena jika Anda meletakkan KeyDowndan KeyUpkejadian di sana, itu menunjukkan bahwa mereka hanya dipecat sekali (masing-masing) tetapi TextBoxChangedacara dipecat dua kali

pengurus
sumber
@undertakeror: Terima kasih telah memeriksa bagian itu. Saya akan mengajukan pertanyaan yang sama di forum khusus WP7 dan melihat apa tanggapannya ...
Jon Skeet
Apa yang dilakukan TextInput? Ini sepertinya bug yang cukup besar untuk lolos dari tes unit WP7, tapi kemudian SL
Chris S
@ Chris S: Apa yang Anda maksud dengan "Apa yang TextInputdilakukan?" Saya tidak akrab dengan TextInput...
Jon Skeet
@Jon `OnTextInput (TextCompositionEventArgs e)` adalah cara SL menangani input teks alih-alih KeyDown, karena jelas perangkat mungkin tidak memiliki keyboard: "Terjadi saat elemen UI mendapatkan teks dengan cara yang tidak bergantung perangkat" msdn.microsoft. com / en-us / library /…
Chris S
Saya hanya ingin tahu apakah itu juga menembak dua kali
Chris S
8

Itu terdengar seperti bug bagi saya. Sebagai solusinya, Anda selalu bisa menggunakan Rx's DistinctUntilChanged. Ada kelebihan beban yang memungkinkan Anda menentukan kunci yang berbeda.

Metode ekstensi ini mengembalikan peristiwa TextChanged yang dapat diamati tetapi melewatkan duplikat yang berurutan:

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

Setelah bug diperbaiki, Anda cukup menghapus DistinctUntilChangedbaris tersebut.

Richard Szalay
sumber
2

Bagus! Saya menemukan pertanyaan ini dengan mencari masalah terkait dan juga menemukan hal yang mengganggu ini di kode saya. Acara ganda memakan lebih banyak sumber daya CPU dalam kasus saya. Jadi, saya memperbaiki kotak teks filter waktu nyata saya dengan solusi ini:

private string filterText = String.Empty;

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

}
crea7or
sumber
1

Saya yakin ini selalu menjadi bug di Compact Framework. Itu pasti telah terbawa ke WP7.

Jerod Houghtelling
sumber
Saya pikir itu sudah diperbaiki di versi yang lebih baru dari CF ... dan akan aneh untuk masuk meskipun pindah ke Silverlight. Di sisi lain, itu bug yang cukup aneh untuk dilihat ...
Jon Skeet
Saya setuju bahwa itu aneh. Saya memutarnya kembali kemarin di aplikasi CF 2.0.
Jerod Houghtelling
0

Tentu tampak seperti bug bagi saya, jika Anda mencoba memunculkan acara setiap kali teks berubah, Anda dapat mencoba menggunakan penjilidan dua arah, sayangnya ini tidak akan memunculkan peristiwa perubahan pers per tombol (hanya jika bidang kehilangan fokus). Berikut solusi jika Anda membutuhkannya:

        this.textBox1.TextChanged -= this.TextBoxChanged;
        textBlock1.Text += "Text changed " + (counter++) + "\r\n";
        this.textBox1.TextChanged += this.TextBoxChanged;
DOA Flatliner
sumber
Saya tidak yakin itu akan berhasil - masalahnya bukan pada event handler yang diaktifkan karena textBlock1.Textperubahan - saya akan mencobanya. (Solusi yang akan saya coba adalah membuat eventhandler saya menjadi stateful, mengingat teks sebelumnya. Jika belum benar-benar berubah, abaikan :)
Jon Skeet
0

Penafian- Saya tidak terbiasa dengan nuansa xaml dan saya tahu ini kedengarannya tidak masuk akal ... tapi bagaimanapun- pikiran pertama saya adalah mencoba menyampaikan hanya sebagai eventarg daripada textchangedeventargs. Tidak masuk akal, tapi mungkin bisa membantu? Sepertinya ketika saya telah melihat penembakan ganda seperti ini sebelumnya bahwa itu baik karena bug atau karena entah bagaimana menambahkan panggilan penangan kejadian terjadi di belakang layar ... Saya tidak yakin yang mana?

Jika Anda perlu cepat dan kotor, sekali lagi, saya tidak berpengalaman dengan xaml- langkah saya selanjutnya adalah melewatkan xaml untuk kotak teks itu sebagai solusi cepat ... lakukan kotak teks itu sepenuhnya di c # untuk saat ini sampai Anda dapat menunjukkan bug atau kode rumit ... yaitu, jika Anda membutuhkan solusi sementara.

Jus McJones Pimp
sumber
Saya bukan orang yang menyampaikan argumen acara apa pun - Saya mengimplementasikan penangan acara. Tapi saya telah memverifikasi bahwa menambahkan event handler murni di C # tidak ada bedanya ... itu masih dipecat dua kali.
Jon Skeet
Oke, hmmm. Ya, jika murni c # maka kedengarannya lebih seperti bug. Tentang dengan saran pertama- Saya minta maaf verbage saya mengerikan, bagaimana saya seharusnya menyatakan adalah- Saya akan mencoba [dalam implementasi / metode penanganan TextBoxChanged] mengubah tipe parameter args menjadi hanya eventargs biasa. Mungkin tidak akan berhasil ... tapi hei ... itu hanya pikiran pertamaku.
Pimp Juice McJones
Dengan kata lain, itu mungkin tidak akan berhasil tetapi saya akan mencoba metode signature = private void TextBoxChanged (pengirim objek, EventArgs e) hanya untuk mengatakan bahwa saya mencobanya =)
Pimp Juice McJones
Baik. Saya rasa itu tidak akan efektif, saya khawatir.
Jon Skeet
0

Saya tidak berpikir itu adalah bug .. Ketika Anda menetapkan nilai ke properti teks di dalam acara textchanged, nilai kotak teks berubah yang akan memanggil lagi acara teks berubah ..

coba ini di Aplikasi Formulir Windows, Anda mungkin mendapatkan kesalahan

"Pengecualian tidak tertangani dari jenis 'System.StackOverflowException' terjadi di System.Windows.Forms.dll"

Senthil Kumar B
sumber
Dari pertanyaan: "Saya baru saja menambahkan TextBox dan TextBlock ke template UI standar" - keduanya bukanlah hal yang sama. Saya punya satu TextBox yang bisa diketik pengguna, dan satu TextBlock yang menampilkan hitungan.
Jon Skeet
0

StefanWick benar, pertimbangkan untuk menggunakan template ini

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

Ini adalah topik lama, tetapi alih-alih mengganti template (yang tidak berfungsi untuk saya, saya tidak melihat kotak teks lain dengan Blend) Anda dapat menambahkan boolean untuk memeriksa apakah acara sudah berfungsi atau tidak.

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

Saya sadar itu BUKAN cara yang sempurna, tapi menurut saya itu cara sederhana untuk melakukannya. Dan itu berhasil.

TDK
sumber