Sự kiện TextBox.TextChanged kích hoạt hai lần trên trình giả lập Windows Phone 7


91

Tôi có một ứng dụng thử nghiệm rất đơn giản chỉ để chơi với Windows Phone 7. Tôi vừa thêm a TextBoxvà a TextBlockvào mẫu giao diện người dùng chuẩn. Mã tùy chỉnh duy nhất như sau:

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

Sự TextBox.TextChangedkiện được kết nối với TextBoxChangedXAML:

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

Tuy nhiên, mỗi khi tôi nhấn một phím khi chạy trong trình giả lập (bàn phím ảo hoặc bàn phím vật lý, đã nhấn Tạm dừng để bật phím sau), nó sẽ tăng bộ đếm hai lần, hiển thị hai dòng trong TextBlock. Mọi thứ tôi đã thử cho thấy rằng sự kiện đang thực sự diễn ra hai lần và tôi không biết tại sao. Tôi đã xác minh rằng nó chỉ được đăng ký một lần - nếu tôi hủy đăng ký trong phương thức MainPagekhởi tạo, không có gì xảy ra cả (với khối văn bản) khi văn bản thay đổi.

Tôi đã thử mã tương đương trong ứng dụng Silverlight thông thường và nó không xảy ra ở đó. Tôi không có điện thoại thực để tái tạo điều này vào lúc này. Tôi không tìm thấy bất kỳ bản ghi nào cho thấy đây là sự cố đã biết trong Windows Phone 7.

Bất cứ ai có thể giải thích những gì tôi đang làm sai, hoặc tôi nên báo cáo điều này là một lỗi?

CHỈNH SỬA: Để giảm khả năng điều này xuống có hai điều khiển văn bản, tôi đã thử loại bỏ TextBlockhoàn toàn và thay đổi phương thức TextBoxChanged thành chỉ tăng dần counter. Sau đó, tôi chạy trong trình giả lập, nhập 10 chữ cái và sau đó đặt một điểm ngắt trên counter++;dòng (chỉ để loại bỏ bất kỳ khả năng nào xâm nhập vào trình gỡ lỗi gây ra sự cố) - và nó cho thấycounter là 20.

CHỈNH SỬA: Bây giờ tôi đã hỏi trong diễn đàn Windows Phone 7 ... chúng ta sẽ xem điều gì sẽ xảy ra.


Chỉ cần quan tâm - nếu bạn kiểm tra bên trong sự kiện, nội dung của TextBox có giống nhau cả hai lần sự kiện kích hoạt không? Tôi thực sự không biết tại sao điều này lại xảy ra, vì tôi thường sử dụng MVVM và liên kết dữ liệu thay vì xử lý sự kiện cho những việc này (Silverlight và WPF, không có nhiều kinh nghiệm với WP7).
Rune Jacobsen

@Rune: Vâng, tôi thấy dòng chữ "sau" hai lần. Vì vậy, nếu tôi nhấn "h" và hiển thị textBox1.Textnhư một phần của phần bổ sung textBlock1, nó sẽ hiển thị "h" ở cả hai dòng.
Jon Skeet

1
Bạn đề cập đến 2 bàn phím, đó có thể là một yếu tố? Bạn có thể vô hiệu hóa một? Và có lẽ bạn có thể kiểm tra xem tất cả các thành viên của TextChangedEventArgs có bình đẳng trong cả hai cuộc gọi hay không?
Henk Holterman

@Henk: Hầu như tôi không bận tâm đến việc bật bàn phím vật lý ... chỉ để xem liệu điều đó có ảnh hưởng gì không. TextChangedEventArgskhông thực sự có sẵn nhiều - chỉ là OriginalSource, luôn luôn rỗng.
Jon Skeet

3
Nó trông giống như một lỗi, nó không liên quan đến bàn phím vì bạn có thể nhận được kết quả tương tự bằng cách chỉ cần gán một giá trị mới cho thuộc tính Text, TextChanged vẫn kích hoạt hai lần.
AnthonyWJones

Câu trả lời:


75

Lý do TextChangedsự kiện kích hoạt hai lần trong WP7 là một tác dụng phụ của cáchTextBox tạo mẫu cho giao diện Metro.

Nếu bạn chỉnh sửa TextBoxmẫu trong Blend, bạn sẽ thấy rằng nó chứa mộtTextBox cho trạng thái vô hiệu hóa / chỉ đọc. Điều này gây ra, như một tác dụng phụ, sự kiện cháy hai lần.

Bạn có thể thay đổi mẫu để loại bỏ các TextBoxtrạng thái bổ sung (và các trạng thái được liên kết) nếu bạn không cần các trạng thái này hoặc sửa đổi mẫu để có giao diện khác ở trạng thái vô hiệu hóa / chỉ đọc mà không cần sử dụng trạng thái phụ TextBox.

Cùng với đó, sự kiện sẽ chỉ diễn ra một lần.


18

tôi sẽ tìm lỗi, chủ yếu là vì nếu bạn đặt các sự kiện KeyDownKeyUpvào đó, nó cho thấy rằng chúng chỉ bị kích hoạt một lần (mỗi người trong số chúng) nhưng TextBoxChangedsự kiện được kích hoạt hai lần


@undertakeror: Cảm ơn bạn đã kiểm tra bit đó. Tôi sẽ hỏi cùng một câu hỏi trên diễn đàn WP7 cụ thể và xem những gì phản ứng là ...
Jon Skeet

TextInput làm gì? Những có vẻ như là một lỗi lớn khá để trượt qua các bài kiểm tra đơn vị của WP7, nhưng sau đó nó là SL
Chris S

@Chris S: Ý bạn là gì khi nói "What does TextInputdo?" Tôi không quen với TextInput...
Jon Skeet

@Jon `OnTextInput (TextCompositionEventArgs e)` là cách SL xử lý nhập văn bản thay vì KeyDown, vì rõ ràng thiết bị có thể không có bàn phím: "Xảy ra khi phần tử giao diện người dùng nhận văn bản theo cách độc lập với thiết bị" msdn.microsoft. com / en-us / library /…
Chris S

Tôi chỉ tò mò liệu nó có nổ hai lần nữa không
Chris S

8

Điều đó nghe có vẻ như một lỗi đối với tôi. Để giải quyết vấn đề, bạn luôn có thể sử dụng Rx'sDistinctUntilChanged . Có một quá tải cho phép bạn chỉ định khóa riêng biệt.

Phương thức mở rộng này trả về sự kiện TextChanged có thể quan sát được nhưng bỏ qua các bản sao liên tiếp:

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

Sau khi lỗi được sửa, bạn có thể chỉ cần xóa DistinctUntilChangeddòng.


2

Đẹp! Tôi đã tìm thấy câu hỏi này bằng cách tìm kiếm một vấn đề liên quan và cũng tìm thấy điều khó chịu này trong mã của tôi. Sự kiện kép ăn nhiều tài nguyên CPU hơn trong trường hợp của tôi. Vì vậy, tôi đã sửa hộp văn bản bộ lọc thời gian thực của mình bằng giải pháp này:

private string filterText = String.Empty;

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

}

1

Tôi tin rằng đây luôn là một lỗi trong Khung nhỏ gọn. Nó phải được chuyển sang WP7.


Tôi nghĩ rằng nó đã được sửa trong một phiên bản mới hơn của CF ... và sẽ thật kỳ lạ khi vào được bất chấp việc chuyển sang Silverlight. Mặt khác, đó là một lỗi khá lạ để xem nào ...
Jon Skeet

Tôi đồng ý rằng nó là lạ. Tôi đã làm lại nó ngày hôm qua trong một ứng dụng CF 2.0.
Jerod Houghtelling

0

Chắc chắn có vẻ như một lỗi đối với tôi, nếu bạn đang cố gắng tăng một sự kiện mỗi khi văn bản thay đổi, bạn có thể thử sử dụng liên kết hai chiều để thay thế, rất tiếc điều này sẽ không tăng sự kiện thay đổi trên mỗi phím bấm (chỉ khi trường mất tập trung). Đây là một cách giải quyết nếu bạn cần:

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

Tôi không chắc rằng điều đó sẽ giải quyết được vấn đề - vấn đề không phải là trình xử lý sự kiện kích hoạt do textBlock1.Textthay đổi - tôi sẽ thử. (Cách giải quyết Tôi đã đi thử là làm stateful eventhandler của tôi, nhớ lại những văn bản trước đó Nếu nó đã không thực sự thay đổi, bỏ qua nó :).
Jon Skeet

0

Tuyên bố từ chối trách nhiệm- Tôi không quen thuộc với các sắc thái xaml và tôi biết điều này nghe có vẻ phi logic ... nhưng dù sao- suy nghĩ đầu tiên của tôi là thử chuyển qua chỉ là các eventargs đơn giản hơn là textchangedeventargs. Không có ý nghĩa, nhưng có thể nó có thể giúp đỡ? Có vẻ như khi tôi đã nhìn thấy những sự cố kép như thế này trước đây rằng đó là do lỗi hoặc do bằng cách nào đó 2 lệnh xử lý sự kiện thêm xảy ra ở hậu trường ... Tuy nhiên, tôi không chắc chắn điều đó?

Nếu bạn cần nhanh chóng và bẩn thỉu, một lần nữa, tôi chưa có kinh nghiệm với xaml- bước tiếp theo của tôi là bỏ qua xaml cho hộp văn bản đó như một giải pháp nhanh chóng ... hãy làm hoàn toàn hộp văn bản đó trong c # cho đến khi bạn có thể xác định lỗi hoặc mã phức tạp ... đó là, nếu bạn cần một giải pháp tạm thời.


Tôi không phải là người chuyển bất kỳ chuỗi sự kiện nào - tôi đang triển khai một trình xử lý sự kiện. Nhưng tôi đã xác minh rằng việc thêm trình xử lý sự kiện hoàn toàn trong C # không có gì khác biệt ... nó vẫn bị kích hoạt hai lần.
Jon Skeet

Được rồi, hmmm. Vâng, nếu đó là một c # thuần túy thì nó giống như một lỗi hơn. Về gợi ý đầu tiên- Tôi xin lỗi vì đống rác của tôi quá khủng khiếp, tôi nên nói như thế nào là- Tôi sẽ thử [trong phương thức trình xử lý / triển khai TextBoxChanged của bạn] thay đổi kiểu tham số args thành chỉ các eventargs đơn thuần. Có lẽ sẽ không hiệu quả ... nhưng này ... đó chỉ là suy nghĩ đầu tiên của tôi.
Pimp Juice McJones

Nói cách khác, nó có lẽ sẽ không làm việc nhưng tôi muốn thử phương pháp chữ ký = private void TextBoxChanged (object sender, EventArgs e) chỉ để nói rằng tôi đã thử nó =)
Pimp Juice McJones

Đúng. Tôi e rằng điều đó sẽ không hiệu quả.
Jon Skeet

0

Tôi không nghĩ đó là lỗi .. Khi bạn gán giá trị cho thuộc tính văn bản bên trong sự kiện đã thay đổi văn bản, giá trị hộp văn bản sẽ bị thay đổi và sẽ gọi lại sự kiện thay đổi văn bản ..

hãy thử điều này trong Ứng dụng Windows Forms, bạn có thể gặp lỗi

"Đã xảy ra một ngoại lệ chưa được xử lý của loại 'System.StackOverflowException' trong System.Windows.Forms.dll"


Từ câu hỏi: "Tôi vừa thêm TextBox và TextBlock vào mẫu giao diện người dùng tiêu chuẩn" - chúng không giống nhau. Tôi có một TextBox mà người dùng có thể nhập và một TextBlock hiển thị số lượng.
Jon Skeet

0

StefanWick nói đúng, hãy cân nhắc sử dụng mẫu này

<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

Đó là một chủ đề cũ, nhưng thay vì thay đổi mẫu (điều đó không phù hợp với tôi, tôi không thấy hộp văn bản khác với Blend), bạn có thể thêm boolean để kiểm tra xem sự kiện đã thực hiện chức năng hay chưa.

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

Tôi biết đó KHÔNG phải là cách hoàn hảo, nhưng tôi nghĩ đó là cách đơn giản để làm điều đó. Và nó hoạt động.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.