TextBox.TextChanged เหตุการณ์ที่เริ่มทำงานสองครั้งบนโปรแกรมจำลอง Windows Phone 7


91

ฉันมีแอปทดสอบที่ง่ายมากเพื่อเล่นกับ Windows Phone 7 ฉันเพิ่งเพิ่ม a TextBoxและ a TextBlockลงในเทมเพลต UI มาตรฐาน โค้ดที่กำหนดเองมีดังต่อไปนี้:

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

แก้ไข: ตอนนี้ฉันถามในฟอรัม Windows Phone 7 ... เราจะดูว่าเกิดอะไรขึ้น


เพียงแค่ไม่สนใจ - หากคุณตรวจสอบภายในงานเนื้อหาของกล่องข้อความเหมือนกันทั้งสองครั้งที่เหตุการณ์เริ่มขึ้นหรือไม่ ฉันไม่รู้จริงๆว่าทำไมสิ่งนี้ถึงเกิดขึ้นเนื่องจากฉันมักใช้ MVVM และการเชื่อมโยงข้อมูลแทนการจัดการเหตุการณ์สำหรับสิ่งเหล่านี้ (Silverlight และ WPF ไม่ค่อยมีประสบการณ์กับ WP7)
Rune Jacobsen

@ รูน: ใช่ฉันเห็นข้อความ "หลัง" สองครั้ง ดังนั้นถ้าฉันกด "h" และแสดงtextBox1.Textเป็นส่วนหนึ่งของการเพิ่ม textBlock1 มันจะแสดง "h" ทั้งสองบรรทัด
Jon Skeet

1
คุณพูดถึงคีย์บอร์ด 2 ตัวนั่นอาจเป็นปัจจัยได้หรือไม่? คุณสามารถปิดการใช้งานได้หรือไม่? และคุณสามารถตรวจสอบได้ว่าสมาชิกทั้งหมดของ TextChangedEventArgs เท่ากันในทั้งสองสายหรือไม่
Henk Holterman

@Henk: ส่วนใหญ่แล้วฉันไม่ได้ใส่ใจในการเปิดใช้งานแป้นพิมพ์จริง ... เพียงเพื่อดูว่าจะมีผลหรือไม่ TextChangedEventArgsมีไม่มากนัก - เพียงแค่ค่าOriginalSourceซึ่งเป็นโมฆะเสมอ
Jon Skeet

3
ดูเหมือนข้อบกพร่องไม่เกี่ยวข้องกับแป้นพิมพ์เนื่องจากคุณสามารถได้รับผลลัพธ์เดียวกันเพียงกำหนดค่าใหม่ให้กับคุณสมบัติข้อความ TextChanged จะยังคงทำงานสองครั้ง
AnthonyWJones

คำตอบ:


75

สาเหตุที่TextChangedเหตุการณ์เริ่มทำงานสองครั้งใน WP7 เป็นผลข้างเคียงของการสร้างTextBoxเทมเพลตสำหรับรูปลักษณ์ของ Metro

หากคุณแก้ไขTextBoxเทมเพลตใน Blend คุณจะเห็นว่าเทมเพลตนั้นมีสถานะสำรองTextBoxสำหรับสถานะปิดใช้งาน / อ่านอย่างเดียว ซึ่งเป็นสาเหตุของผลข้างเคียงเหตุการณ์ที่จะเริ่มทำงานสองครั้ง

คุณสามารถเปลี่ยนเทมเพลตเพื่อลบส่วนเกินได้ TextBox (และสถานะที่เกี่ยวข้อง) ออกได้หากคุณไม่ต้องการสถานะเหล่านี้หรือแก้ไขเทมเพลตเพื่อให้ได้รูปลักษณ์ที่แตกต่างกันในสถานะปิดใช้งาน / อ่านอย่างเดียวโดยไม่ต้องใช้สถานะสำรองTextBoxปิดการใช้งานแบบอ่านอย่างเดียวของรัฐโดยไม่ต้องใช้รอง

ด้วยเหตุนี้เหตุการณ์จะเริ่มขึ้นเพียงครั้งเดียว


18

ฉันจะไปหาจุดบกพร่องส่วนใหญ่เพราะถ้าคุณใส่KeyDownและKeyUpเหตุการณ์ลงในนั้นแสดงว่าพวกเขาถูกยิงเพียงครั้งเดียว (แต่ละรายการ) แต่TextBoxChangedเหตุการณ์จะถูกยิงสองครั้ง


@undertakeror: ขอบคุณสำหรับการตรวจสอบบิตนั้น ฉันจะถามคำถามเดียวกันในฟอรัมเฉพาะ WP7 และดูว่าคำตอบคืออะไร ...
Jon Skeet

TextInput ทำอะไร? สิ่งเหล่านี้ดูเหมือนจะเป็นข้อผิดพลาดที่ยิ่งใหญ่ที่จะผ่านการทดสอบหน่วยของ WP7 แต่แล้วก็เป็น SL
Chris S

@ คริส S: คุณหมายถึงอะไร "What does TextInput?" ฉันไม่คุ้นเคยกับTextInput...
Jon Skeet

@Jon `OnTextInput (TextCompositionEventArgs e)` เป็นวิธี SL ในการจัดการการป้อนข้อความแทน KeyDown เนื่องจากเห็นได้ชัดว่าอุปกรณ์อาจไม่มีแป้นพิมพ์: "เกิดขึ้นเมื่อองค์ประกอบ UI ได้รับข้อความในลักษณะที่ไม่ขึ้นกับอุปกรณ์" msdn.microsoft com / en-us / library / …
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

ดี! ฉันพบคำถามนี้จากการค้นหาปัญหาที่เกี่ยวข้องและพบสิ่งที่น่ารำคาญนี้ในโค้ดของฉัน เหตุการณ์สองครั้งกินทรัพยากร CPU มากขึ้นในกรณีของฉัน ดังนั้นฉันจึงแก้ไขกล่องข้อความตัวกรองแบบเรียลไทม์ด้วยวิธีนี้:

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การเปลี่ยนแปลงฉันจะลองดู (วิธีแก้ปัญหาที่ฉันกำลังจะลองคือทำให้ตัวจัดการเหตุการณ์ของฉันมีสถานะจำข้อความก่อนหน้านี้ได้หากยังไม่เปลี่ยนแปลงจริงก็ไม่ต้องสนใจ :)
Jon Skeet

0

ข้อจำกัดความรับผิดชอบ - ฉันไม่คุ้นเคยกับความแตกต่างของ xaml และฉันรู้ว่าสิ่งนี้ฟังดูไร้เหตุผล ... แต่อย่างไรก็ตาม - ความคิดแรกของฉันคือลองส่งผ่านเป็นเหตุการณ์ธรรมดา ๆ แทนที่จะเป็น textchangedeventargs ไม่สมเหตุสมผล แต่อาจช่วยได้ไหม? ดูเหมือนว่าเมื่อฉันเคยเห็นการจับคู่แบบนี้มาก่อนนั้นอาจเกิดจากข้อผิดพลาดหรือเนื่องจากการเรียกตัวจัดการเหตุการณ์เพิ่ม 2 ครั้งเกิดขึ้นเบื้องหลัง ... ฉันไม่แน่ใจว่าเป็นอย่างไร

หากคุณต้องการความรวดเร็วและสกปรกอีกครั้งฉันไม่มีประสบการณ์กับ xaml ขั้นตอนต่อไปของฉันคือเพียงข้าม xaml สำหรับกล่องข้อความนั้นเป็นวิธีแก้ปัญหาอย่างรวดเร็ว ... ทำ textbox ทั้งหมดใน c # ในตอนนี้จนกว่าคุณจะสามารถระบุจุดบกพร่องหรือ รหัสที่ยุ่งยาก ... นั่นคือหากคุณต้องการวิธีแก้ปัญหาชั่วคราว


ฉันไม่ใช่คนที่ส่งผ่านเหตุการณ์ใด ๆ - ฉันกำลังใช้งานตัวจัดการเหตุการณ์ แต่ฉันได้ตรวจสอบแล้วว่าการเพิ่มตัวจัดการเหตุการณ์อย่างหมดจดใน C # ไม่ทำให้เกิดความแตกต่าง ... มันยังคงถูกยิงสองครั้ง
Jon Skeet

ตกลงอืม ใช่ถ้ามันเป็น c # ที่บริสุทธิ์มันก็ฟังดูเป็นบั๊กมากกว่า เกี่ยวกับคำแนะนำแรก - ฉันขอโทษที่ verbage ของฉันน่ากลัววิธีที่ฉันควรระบุคือ - ฉันจะลอง [ในการใช้งาน / วิธีจัดการ TextBoxChanged ของคุณ] เปลี่ยนประเภทพารามิเตอร์ args เป็นเพียงเหตุการณ์ธรรมดา อาจจะใช้ไม่ได้ ... แต่เดี๋ยวก่อน ... มันเป็นเพียงความคิดแรกของฉัน
Pimp Juice McJones

กล่าวอีกนัยหนึ่งมันอาจจะใช้ไม่ได้ แต่ฉันจะลองใช้ method signature = private void TextBoxChanged (object sender, EventArgs e) เพียงเพื่อบอกว่าฉันลองแล้ว =)
Pimp Juice McJones

ขวา. ฉันไม่คิดว่ามันจะมีประสิทธิภาพฉันกลัว
Jon Skeet

0

ฉันไม่คิดว่ามันเป็นจุดบกพร่อง .. เมื่อคุณกำหนดค่าให้กับคุณสมบัติข้อความภายในเหตุการณ์ที่เปลี่ยนแปลงข้อความค่ากล่องข้อความจะเปลี่ยนไปซึ่งจะเรียกเหตุการณ์ที่เปลี่ยนแปลงข้อความอีกครั้ง ..

ลองใช้ใน Windows Forms Application คุณอาจได้รับข้อผิดพลาด

"ข้อยกเว้นที่ไม่สามารถจัดการได้ของประเภท 'System.StackOverflowException' เกิดขึ้นใน System.Windows.Forms.dll"


จากคำถาม: "ฉันเพิ่งเพิ่ม TextBox และ TextBlock ลงในเทมเพลต UI มาตรฐาน" - ไม่ใช่สิ่งเดียวกัน ฉันมีกล่องข้อความหนึ่งกล่องที่ผู้ใช้สามารถพิมพ์ลงไปได้และหนึ่งกล่องข้อความที่แสดงจำนวน
Jon Skeet

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

ฉันรู้ว่านั่นไม่ใช่วิธีที่สมบูรณ์แบบ แต่ฉันคิดว่ามันเป็นวิธีง่ายๆในการทำเช่นนั้น และได้ผล

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.