Windows Phone 7模拟器上触发两次TextBox.TextChanged事件


91

我有一个非常简单的测试应用程序,可以与Windows Phone 7一起使用。我刚刚在标准UI模板中添加了TextBoxTextBlock。唯一的自定义代码如下:

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事件连接到TextBoxChangedXAML中:

<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中找到任何已知问题的记录。

谁能解释我在做什么错,还是应该将其报告为错误?

编辑:为了减少这种被降低到具有两个文本控件的可能性,我已经试图消除TextBlock完全,并改变TextBoxChanged方法只是增量counter。然后,我在仿真器中运行,键入10个字母,然后在该counter++;行上放置一个断点(只是为了消除闯入调试器会引起问题的任何可能性),它显示counter为20。

编辑:我现在已经在Windows Phone 7论坛中问了 ……我们将看看会发生什么。


只是出于兴趣-如果您在事件内部进行检查,则两次触发事件时TextBox的内容都相同吗?我真的不知道为什么会这样,因为我通常使用MVVM和数据绑定而不是事件处理(Silverlight和WPF,对WP7经验不足)。
符文雅各布森

@Rune:是的,我两次看到“ after”文本。因此,如果我按“ h”并显示textBox1.Text为textBlock1添加的一部分,它将在两行中显示“ h”。
乔恩·斯基特

1
您提到2个键盘,这可能是一个因素吗?你能禁用一个吗?也许您可以检查两个调用中TextChangedEventArgs的所有成员是否相等?
Henk Holterman

@Henk:大多数时候,我并没有费心启用物理键盘...只是看看它是否会起作用。TextChangedEventArgs实际上并没有太多可用-只是OriginalSource,始终为null。
乔恩·斯基特

3
它的确看起来像个错误,它与键盘无关,因为您只需将新值分配给Text属性即可获得相同的结果,TextChanged仍会触发两次。
AnthonyWJones 2010年

Answers:


75

TextChanged事件在WP7中触发两次的原因是TextBox为Metro外观模板化的。

如果您TextBox在Blend中编辑模板,则会看到它包含一个辅助模板TextBox用于禁用/只读状态。作为副作用,这将导致事件触发两次。

TextBox如果不需要这些状态,可以更改模板以删除多余的(和关联的状态),也可以修改模板以在禁用/只读状态下获得不同的外观,而无需使用secondary TextBox

这样,该事件将仅触发一次。


18

我会去找bug,主要是因为如果您将KeyDownand KeyUp事件放在其中,则表明它们仅被触发一次(每个TextBoxChanged事件),但是事件被触发了两次


@undertakeror:感谢您检查该位。我将在WP7特定论坛上问相同的问题,然后看看响应是什么...
Jon Skeet 2010年

TextInput是做什么的?通过WP7的单元测试,这些漏洞似乎是一个很大的错误,但是它是SL
Chris S

@Chris S:“做什么”是什么意思TextInput?我不熟悉TextInput...
Jon Skeet 2010年

@Jon`OnTextInput(TextCompositionEventArgs e)`是处理文本输入而不是KeyDown的SL方法,因为显然该设备可能没有键盘:“当UI元素以与设备无关的方式获取文本时发生” msdn.microsoft。 com / zh-CN / library /…
克里斯·S

我只是好奇这是否还会触发两次
克里斯·S,2010年

8

在我看来,这确实像个虫子。解决方法是,您始终可以使用Rx's 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

真好!我通过搜索相关问题找到了这个问题,并且在我的代码中也发现了这个烦人的事情。在我的情况下,双重事件会占用更多的CPU资源。因此,我使用以下解决方案修复了我的实时过滤器文本框:

private string filterText = String.Empty;

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

}


0

当然,对于我来说,这似乎是个错误,如果您每次文本更改时都尝试引发一个事件,则可以尝试使用双向绑定,但是不幸的是,这不会引发每次按键更改事件(仅当该字段失去焦点)。如果您需要一种解决方法,请执行以下操作:

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

我不确定这是否可以解决-问题不是事件处理程序因textBlock1.Text更改而触发-我会尝试一下。(要尝试的解决方法是使事件处理程序处于有状态状态,并记住前面的文本。如果尚未真正更改,请忽略它:)
Jon Skeet 2010年

0

免责声明-我不熟悉xaml细微差别,而且我知道这听起来不合逻辑...但是,无论如何-我的第一个想法是尝试仅作为普通eventarg而不是textchangedeventargs进行传递。没道理,但可能会有所帮助吗?好像我之前看到过这样的两次触发是由于错误或由于某种原因在幕后发生了2个添加事件处理程序调用而导致的……我不确定是哪一个?

再次,如果您需要快速又肮脏的话,那么我将不熟悉xaml,我的下一步就是跳过该文本框的xaml,这是一种快速的解决方法...暂时完全在c#中执行该文本框,直到您可以找出错误或棘手的代码...也就是说,如果您需要临时解决方案。


我不是传递任何事件arg的人-我正在实现一个事件处理程序。但是我已经证实,纯粹在C#中添加事件处理程序没有什么区别……它仍然被触发两次。
乔恩·斯基特

好,嗯 是的,如果它是纯C#,则听起来更像是个错误。关于第一个建议-抱歉我的话语太恐怖了,我应该这么说是-我会尝试[在您的实现/ TextBoxChanged处理程序方法中]将args参数类型更改为仅普通的eventargs。可能行不通...但是,嘿...这只是我的第一个念头。
皮条客果汁McJones 2010年

换句话说,它可能行不通,但我会尝试方法签名=私有void TextBoxChanged(object sender,EventArgs e)只是说我尝试过=)
Pimp Juice McJones 2010年

对。恐怕我认为这不会产生任何效果。
乔恩·斯基特

0

我不认为这是一个错误..当您将值分配给textchanged事件内的text属性时,textbox值将更改,这将再次称为text change事件。

在Windows窗体应用程序中尝试此操作,可能会收到错误消息

“在System.Windows.Forms.dll中发生了类型为'System.StackOverflowException'的未处理异常”


从问题开始:“我刚刚将TextBox和TextBlock添加到标准UI模板中”-它们不是一回事。我有一个可供用户键入的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;
    }
}

我知道这不是完美的方法,但我认为这是简单的方法。而且有效。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.