TextBox.TextChanged olay, Windows Phone 7 öykünücüsünde iki kez tetikleniyor


91

Sadece Windows Phone 7 ile oynamak için çok basit bir test uygulamam var . Standart kullanıcı arayüzü şablonuna bir TextBoxve a ekledim TextBlock. Tek özel kod şudur:

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.TextChangedOlay kadar kablolu TextBoxChangedXAML:

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

Bununla birlikte, öykünücüde çalışırken bir tuşa her bastığımda (ekran klavyesi veya fiziksel olanı, ikincisini etkinleştirmek için Duraklat'a bastıktan sonra), sayacı iki kez artırarak TextBlock. Denediğim her şey, olayın gerçekten iki kez ateşlendiğini gösteriyor ve neden olduğuna dair hiçbir fikrim yok. Sadece bir kez abone olduğunu doğruladım - kurucuda aboneliği iptal edersem MainPage, metin değiştiğinde hiçbir şey olmuyor (metin bloğuna).

Eşdeğer kodu normal bir Silverlight uygulamasında denedim ve orada olmadı. Şu anda bunu yeniden üretecek fiziksel bir telefonum yok. Windows Phone 7'de bunun bilinen bir sorun olduğuna dair herhangi bir kayıt bulamadım.

Neyi yanlış yaptığımı kimse açıklayabilir mi, yoksa bunu bir hata olarak bildirmeli miyim?

DÜZENLEME: Bunun iki metin kontrolüne sahip olma olasılığını azaltmak için, TextBlocktamamen kaldırmayı ve TextBoxChanged yöntemini sadece artış olarak değiştirmeyi denedim counter. Daha sonra öykünücüyü çalıştırdım, 10 harf yazdım ve sonracounter++; satıra bir kesme noktası koydum (sadece hata ayıklayıcıya girmenin sorunlara neden olma olasılığından kurtulmak için) - ve counter20 olarak gösteriyor .

DÜZENLEME: Şimdi Windows Phone 7 forumunda sordum ... ne olacağını göreceğiz.


Sadece ilgi çekici - olayın içini kontrol ederseniz, TextBox'ın içeriği olay her iki seferde de aynı mı? Bunun neden olacağını gerçekten bilmiyorum, çünkü genellikle bu şeyler için olay işleme yerine MVVM ve veri bağlamayı kullanıyorum (Silverlight ve WPF, WP7 ile fazla deneyim yok).
Rune Jacobsen

@Rune: Evet, "after" metnini iki kez görüyorum. Yani "h" tuşuna textBox1.Textbasar ve textBlock1 toplamasının bir parçası olarak görüntülersem , her iki satırda da "h" gösterecektir.
Jon Skeet

1
2 klavyeden bahsediyorsunuz, bu bir faktör olabilir mi? Birini devre dışı bırakabilir misin? Ve belki TextChangedEventArgs'ın tüm üyelerinin her iki çağrıda da eşit olup olmadığını kontrol edebilirsiniz.
Henk Holterman

@Henk: Çoğu zaman fiziksel klavyeyi etkinleştirmekle uğraşmadım ... sadece bunun bir etkisi olup olmayacağını görmek için. TextChangedEventArgsgerçekten çok fazla mevcut değil - sadece OriginalSourceher zaman boş olan.
Jon Skeet

3
Hata gibi görünüyor, klavye ile ilgili değil çünkü aynı sonuçları sadece Text özelliğine yeni bir değer atayarak alabilirsiniz, TextChanged hala iki kez ateşleniyor.
AnthonyWJones

Yanıtlar:


75

TextChangedOlayın WP7'de iki kez tetiklenmesinin nedeni TextBox, Metro görünümü için nasıl şablon haline getirildiğinin bir yan etkisidir .

TextBoxŞablonu Blend'de düzenlerseniz, TextBoxdevre dışı / salt okunur durum için bir ikincil içerdiğini görürsünüz . Bu, bir yan etki olarak olayın iki kez ateşlenmesine neden olur.

TextBoxBu durumlara ihtiyacınız yoksa fazladan (ve ilişkili durumları) kaldırmak için şablonu değiştirebilir veya ikincil kullanmadan devre dışı / salt okunur durumda farklı bir görünüm elde etmek için şablonu değiştirebilirsiniz TextBox.

Bununla, olay yalnızca bir kez ateşlenecektir.


18

hata için giderdim, çünkü KeyDownve KeyUpolaylarını oraya koyarsanız, yalnızca bir kez (her biri) TextBoxChangedtetiklendiklerini, ancak olayın iki kez tetiklendiğini gösterir.


@undertakeror: Bu kısmı kontrol ettiğiniz için teşekkürler. Ben WP7 özgü forumunda aynı soruyu sormak ve yanıt ... ne göreceksiniz
Jon Skeet

TextInput ne yapar? Bunlar WP7 birim testler aracılığıyla kayma oldukça büyük bir böcek gibi görünüyor, ama o zaman SL'dir
Chris S

@Chris S: "Ne yapar TextInput?" Derken neyi kastediyorsunuz ? Aşina değilim TextInput...
Jon Skeet

@Jon `OnTextInput (TextCompositionEventArgs e)", aygıtın klavyesi olmayabileceğinden, KeyDown yerine metin girişini işlemenin SL yoludur: "Bir UI öğesi cihazdan bağımsız bir şekilde metin aldığında oluşur" msdn.microsoft. com / en-us / library /…
Chris S

Bunun iki kez ateşlenip ateşlenmediğini merak ettim
Chris S

8

Bu bana bir böcek gibi geliyor. Geçici bir çözüm olarak, her zaman Rx'leri kullanabilirsiniz DistinctUntilChanged. Farklı anahtarı belirlemenize izin veren bir aşırı yükleme var.

Bu uzantı yöntemi, gözlemlenebilir TextChanged olayını döndürür ancak ardışık kopyaları atlar:

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

Hata giderildikten sonra DistinctUntilChangedsatırı kaldırmanız yeterlidir .


2

Güzel! Bu soruyu ilgili bir problemi arayarak buldum ve ayrıca bu can sıkıcı şeyi kodumda buldum. Çift olay benim durumumda daha fazla CPU kaynağı yiyor. Bu yüzden, gerçek zamanlı filtre metin kutumu şu çözümle düzelttim:

private string filterText = String.Empty;

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

}

1

Bunun Compact Framework'te her zaman bir hata olduğuna inanıyorum. WP7'ye taşınmış olmalı.


CF'nin daha yeni bir sürümünde düzeltildiğini düşündüm ... ve Silverlight'a taşınmasına rağmen içeri girmek garip olurdu. Öte yandan, yine de görmek oldukça garip bir hata ...
Jon Skeet

Tuhaf olduğuna katılıyorum. Bunu dün bir CF 2.0 uygulamasında yeniden anlattım.
Jerod Houghtelling

0

Elbette bana bir hata gibi görünüyor, eğer metin her değiştiğinde bir olay oluşturmaya çalışıyorsanız bunun yerine iki yönlü bir bağlama kullanmayı deneyebilirsiniz, ne yazık ki bu tuş başına basma değişikliği olaylarını artırmaz (yalnızca alan odağı kaybeder). İhtiyacınız olursa işte bir geçici çözüm:

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

Bunun işe yarayacağından emin değilim - sorun, olay işleyicisinin textBlock1.Textdeğişiklik nedeniyle tetiklenmesi değil - yine de deneyeceğim. (Geçici çözüm Ben denemede gidiyordu önceki metin hatırlayarak, benim eventhandler Durum Bilgisi yapmaktı aslında değişmedi, bunu görmezden :).
Jon Skeet

0

Sorumluluk reddi - xaml nüanslarına aşina değilim ve bunun mantıksız geldiğini biliyorum ... ama yine de - ilk düşüncem, metin değiştirilmiş olaylar yerine sadece düz olay değişkenleri olarak geçmeyi denemektir. Mantıklı gelmiyor ama yardımcı olabilir mi? Daha önce bunun gibi çift ateşleme gördüğümde, ya bir hatadan ya da bir şekilde perde arkasında 2 olay işleyici çağrısı eklenmesinden kaynaklanıyor gibi görünüyor ... Ama hangisi olduğundan emin değilim?

Hızlı ve kirli bir şekilde ihtiyacınız varsa, yine, xaml ile deneyimlenmeme - bir sonraki adımım, hızlı bir geçici çözüm olarak bu metin kutusu için xaml'yi atlamak olacaktır ... bu metin kutusunu şimdilik tamamen c # olarak yapın veya zor kod ... yani, geçici bir çözüme ihtiyacınız varsa.


Herhangi bir olay değiştirgesini ileten ben değilim - Bir olay işleyicisi uyguluyorum. Ancak olay işleyicisini yalnızca C # ile eklemenin hiçbir fark yaratmadığını doğruladım ... yine de iki kez ateşleniyor.
Jon Skeet

Tamam, hmmm. Evet, saf c # ise, o zaman daha çok bir hata gibi geliyor. İlk öneri hakkında - Özür dilerim, sözüm korkunçtu, nasıl ifade etmeliydim - [sizin uygulamanızda / TextBoxChanged işleyici yönteminde] args parametre türünü sadece düz olayargs olarak değiştirmeyi deneyeceğim. Muhtemelen işe yaramayacak ... ama hey ... bu sadece ilk düşüncemdi.
Pimp Juice McJones

Başka bir deyişle, muhtemelen işe yaramayacak, ancak yöntem imzası = özel void TextBoxChanged (nesne gönderen, EventArgs e) denedim, sadece denediğimi söylemek için =)
Pimp Juice McJones

Sağ. Korkarım bunun etkili olacağını sanmıyorum.
Jon Skeet

0

Bunun bir hata olduğunu sanmıyorum .. textchanged olayının içindeki bir text özelliğine değer atadığınızda, textbox değeri değiştirilir ve bu da text değiştirilmiş olayını tekrar çağırır ..

Windows Forms Uygulamasında bunu deneyin, bir hata alabilirsiniz

"System.Windows.Forms.dll'de 'System.StackOverflowException' türünde işlenmemiş bir istisna oluştu"


Sorudan: "Standart UI şablonuna bir TextBox ve TextBlock ekledim" - bunlar aynı şey değil. Kullanıcının yazabileceği bir TextBox ve sayımı gösteren bir TextBlock var.
Jon Skeet

0

StefanWick haklı, bu şablonu kullanmayı düşünün

<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

Bu eski bir konu, ancak değişim şablonu yerine (bu benim için çalışmıyor, Blend ile diğer metin kutusunu görmüyorum) etkinliğin işlevi zaten yapıp yapmadığını kontrol etmek için boole ekleyebilirsiniz.

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

Bunun mükemmel bir yol olmadığının farkındayım, ama bunu yapmanın basit yolu olduğunu düşünüyorum. Ve çalışıyor.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.