Mam bardzo prostą aplikację testową do zabawy z Windows Phone 7. Właśnie dodałem a TextBox
i a TextBlock
do standardowego szablonu UI. Jedyny kod niestandardowy jest następujący:
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
Wydarzenie jest podłączony do TextBoxChanged
w XAML:
<TextBox Height="72" HorizontalAlignment="Left" Margin="6,37,0,0"
Name="textBox1" Text="" VerticalAlignment="Top"
Width="460" TextChanged="TextBoxChanged" />
Jednak za każdym razem, gdy naciskam klawisz podczas pracy w emulatorze (na klawiaturze ekranowej lub fizycznej, po naciśnięciu Pause, aby włączyć tę drugą), dwukrotnie zwiększa licznik, wyświetlając dwie linie w TextBlock
. Wszystko, czego próbowałem, pokazuje, że zdarzenie jest autentycznie odpalane dwukrotnie i nie mam pojęcia, dlaczego. Sprawdziłem, że jest subskrybowany tylko raz - jeśli wypiszę się w MainPage
konstruktorze, nic się nie dzieje (do bloku tekstowego), gdy tekst się zmienia.
Wypróbowałem równoważny kod w zwykłej aplikacji Silverlight i tam go nie było. W tej chwili nie mam fizycznego telefonu, z którego mógłbym to odtworzyć. Nie znalazłem żadnego zapisu, że jest to znany problem w Windows Phone 7.
Czy ktoś może wyjaśnić, co robię źle, czy powinienem zgłosić to jako błąd?
EDYCJA: Aby zredukować możliwość sprowadzenia tego do dwóch kontrolek tekstu, próbowałem TextBlock
całkowicie usunąć i zmienić metodę TextBoxChanged, aby po prostu zwiększyć counter
. Następnie uruchomiłem emulator, wpisałem 10 liter, a następnie umieściłem punkt przerwania w counter++;
linii (tylko po to, aby pozbyć się jakiejkolwiek możliwości, że włamanie do debugera powoduje problemy) - i pokazuje się counter
jako 20.
EDYCJA: Zapytałem teraz na forum Windows Phone 7 ... zobaczymy, co się stanie.
źródło
textBox1.Text
jako część dodania textBlock1, pokaże "h" w obu wierszach.TextChangedEventArgs
tak naprawdę nie ma wielu dostępnych - tylkoOriginalSource
, co zawsze jest zerowe.Odpowiedzi:
Powód, dla którego
TextChanged
zdarzenie uruchamia się dwukrotnie w WP7, jest efektem ubocznym tego, jakTextBox
szablon został utworzony dla wyglądu Metro.Jeśli edytujesz
TextBox
szablon w Blend, zobaczysz, że zawiera on drugorzędnyTextBox
stan wyłączony / tylko do odczytu. Powoduje to, jako efekt uboczny, dwukrotne uruchomienie zdarzenia.Możesz zmienić szablon, aby usunąć dodatkowe
TextBox
(i powiązane stany), jeśli nie potrzebujesz tych stanów, lub zmodyfikować szablon, aby uzyskać inny wygląd w stanie wyłączonym / tylko do odczytu, bez korzystania z dodatkowegoTextBox
.Dzięki temu zdarzenie zostanie uruchomione tylko raz.
źródło
Poszedłbym na błąd, głównie dlatego, że jeśli umieścisz tam zdarzenia
KeyDown
iKeyUp
, to pokazuje, że są odpalane tylko raz (każdy z nich), aleTextBoxChanged
zdarzenie jest uruchamiane dwa razyźródło
TextInput
?” Nie znamTextInput
...To brzmi dla mnie jak błąd. Aby obejść ten problem, zawsze możesz użyć Rx
DistinctUntilChanged
. Istnieje przeciążenie, które umożliwia określenie odrębnego klucza.Ta metoda rozszerzenia zwraca obserwowalne zdarzenie TextChanged, ale pomija kolejne duplikaty:
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); }
Po naprawieniu błędu możesz po prostu usunąć
DistinctUntilChanged
linię.źródło
Ładny! Znalazłem to pytanie, szukając powiązanego problemu, a także znalazłem tę irytującą rzecz w moim kodzie. W moim przypadku zdarzenie podwójne pochłania więcej zasobów procesora. Naprawiłem więc moje pole tekstowe filtru w czasie rzeczywistym za pomocą tego rozwiązania:
private string filterText = String.Empty; private void SearchBoxUpdated( object sender, TextChangedEventArgs e ) { if ( filterText != filterTextBox.Text ) { // one call per change filterText = filterTextBox.Text; ... } }
źródło
Uważam, że zawsze był to błąd w Compact Framework. Musiało zostać przeniesione do WP7.
źródło
Z pewnością wygląda to na błąd, jeśli próbujesz wywołać zdarzenie za każdym razem, gdy zmieni się tekst, możesz zamiast tego spróbować użyć dwukierunkowego wiązania, niestety nie spowoduje to podniesienia zdarzeń zmiany naciśnięcia klawisza (tylko gdy pole traci skupienie). Oto obejście, jeśli potrzebujesz:
this.textBox1.TextChanged -= this.TextBoxChanged; textBlock1.Text += "Text changed " + (counter++) + "\r\n"; this.textBox1.TextChanged += this.TextBoxChanged;
źródło
textBlock1.Text
zmiany - jednak spróbuję. (Obejście ja jechałem spróbować to zrobić mój eventhandler Stateful, pamiętając poprzedni tekst, jeśli nie został faktycznie zmienił go zignorować :).Zastrzeżenie - nie jestem zaznajomiony z niuansami XAML i wiem, że brzmi to nielogicznie ... ale w każdym razie - moją pierwszą myślą jest próba podania jako zwykłych elementów zdarzenia, a nie zmienionych tekstowo zdarzeń. To nie ma sensu, ale może mogłoby pomóc? Wygląda na to, że kiedy widziałem już takie podwójne odpalenia, to albo z powodu błędu, albo z powodu 2 dodania wywołań obsługi zdarzeń za kulisami ... Nie jestem jednak pewien, które?
Jeśli potrzebujesz szybkiego i nieczystego, znowu nie mam doświadczenia z xaml - moim następnym krokiem byłoby po prostu pominięcie xaml dla tego pola tekstowego jako szybkiego obejścia ... zrób to pole tekstowe całkowicie w c # na razie, dopóki nie będziesz mógł wskazać błędu lub trudny kod ... to znaczy, jeśli potrzebujesz tymczasowego rozwiązania.
źródło
Nie sądzę, żeby to był błąd. Kiedy przypiszesz wartość do właściwości tekstowej wewnątrz zdarzenia textchanged, wartość pola tekstowego zostanie zmieniona, co ponownie wywoła zdarzenie zmiany tekstu.
spróbuj tego w aplikacji Windows Forms, możesz otrzymać błąd
„W System.Windows.Forms.dll wystąpił nieobsługiwany wyjątek typu„ System.StackOverflowException ””
źródło
StefanWick ma rację, rozważ użycie tego szablonu
<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>
źródło
To stary temat, ale zamiast zmienić szablon (to nie działa dla mnie, nie widzę drugiego pola tekstowego z Blend) możesz dodać boolean, aby sprawdzić, czy zdarzenie już wykonało funkcję, czy nie.
boolean already = false; private void Tweet_SizeChanged(object sender, EventArgs e) { if (!already) { already = true; ... } else { already = false; } }
Zdaję sobie sprawę, że to NIE jest idealny sposób, ale myślę, że to prosty sposób. I to działa.
źródło