2017-03-02 1 views
1

Ich glaube, ich habe ein Threading-Problem in meiner UWP-App. Ich möchte eine sehr einfache Sache tun:UWP: Ausnahme beim Einstellen einer Eigenschaft direkt nach dem Einstellen einer anderen

  • eine Benutzeroberfläche mit 2 numerischen Feldern;
  • Wenn ein numerischer Wert in field1 eingegeben wird, möchte ich field2 mit einem Verhältnis von field1 (Beispiel: field2 = ratio * field1) festgelegt werden.

Ich verwende x:Bind und Ereignisse. Aus unbekannten Gründen konnte ich im XAML das Ereignis TextChanging nicht aufrufen, ohne beim Start eine Ausnahme zu haben. Daher verwende ich das Ereignis Loaded.

Hier ist meine Modellklasse, einfach MyModel genannt:

public class MyModel : INotifyPropertyChanged 
{ 
    private readonly uint r1 = 3; 

    private uint _field1; 
    public uint Field1 
    { 
     get { return this._field1; } 
     set 
     { 
      this.Set(ref this._field1, value); 

      if (value == 0) 
      { 
       Field2 = 0; 
      } 
      else 
      { 
       Field2 = value * r1; 
      } 
     } 
    } 

    private uint _field2; 
    public uint Field2 
    { 
     get { return this._field2; } 
     set 
     { 
      this.Set(ref this._field2, value); 
     } 
    } 

    public event PropertyChangedEventHandler PropertyChanged; 

    protected void RaisedPropertyChanged([CallerMemberName]string propertyName = null) 
    { 
     PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 
    } 

    protected bool Set<T>(ref T storage, T value, [CallerMemberName]string propertyName = null) 
    { 
     if (Equals(storage, value)) 
     { 
      return false; 
     } 
     else 
     { 
      storage = value; 
      this.RaisedPropertyChanged(propertyName); 
      return true; 
     } 
    } 
} 

Mein Viewmodel:

public class MyModelViewModel : INotifyPropertyChanged 
{ 
    public MyModel MyModel { get; set; } 

    public MyModelViewModel() 
    { 
     // Initialisation de notre page 
     this.MyModel = new MyModel() 
     { 
      Field1 = 0 
     }; 
    } 

    public event PropertyChangedEventHandler PropertyChanged; 

    protected virtual void OnPropertyChanged(string propertyName) 
    { 
     PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 
    } 
} 

mein Code hinter (Ich bin Filtern des Eingangs eine gegossene Ausnahme zu vermeiden):

public sealed partial class MainPage : Page 
{ 
    public MyModelViewModel ViewModel { get; set; } = new MyModelViewModel(); 

    public MainPage() 
    { 
     this.InitializeComponent(); 
    } 

    private void InitField1(object sender, Windows.UI.Xaml.RoutedEventArgs e) 
    { 
     field1.TextChanging += field1_TextChanging; 
    } 

    private void InitField2(object sender, Windows.UI.Xaml.RoutedEventArgs e) 
    { 
     field2.TextChanging += field2_TextChanging; 
    } 

    private void field1_TextChanging(TextBox sender, TextBoxTextChangingEventArgs args) 
    { 
     var error = errorTextBlock; 
     error.Visibility = Windows.UI.Xaml.Visibility.Collapsed; 

     Regex regex = new Regex("[^0-9]+"); // All but numeric 
     if (regex.IsMatch(sender.Text)) 
     { 
      error.Text = "Non numeric char"; 
      error.Visibility = Windows.UI.Xaml.Visibility.Visible; 
      sender.Text = this.ViewModel.MyModel.Field1.ToString(); 
     } 
     else 
     { 
      this.ViewModel.MyModel.Field1 = Convert.ToUInt32(sender.Text); 
     } 
    } 

    private void field2_TextChanging(TextBox sender, TextBoxTextChangingEventArgs args) 
    { 
     var error = errorTextBlock; 
     error.Visibility = Windows.UI.Xaml.Visibility.Collapsed; 

     Regex regex = new Regex("[^0-9]+"); 
     if (regex.IsMatch(sender.Text)) 
     { 
      error.Text = "Non numeric char"; 
      error.Visibility = Windows.UI.Xaml.Visibility.Visible; 
      sender.Text = this.ViewModel.MyModel.Field2.ToString(); 
     } 
     else 
     { 
      this.ViewModel.MyModel.Field2 = Convert.ToUInt32(sender.Text); 
     } 
    } 
} 

Schließlich meine XAML:

<TextBlock Grid.Row="0" Grid.Column="0" x:Name="errorTextBlock" Text="" Visibility="Collapsed" /> 

<TextBlock Grid.Row="1" Grid.Column="0" Text="Field 1" /> 
<TextBox Grid.Row="1" Grid.Column="1" x:Name="field1" Text="{x:Bind ViewModel.MyModel.Field1, Mode=OneWay}" Loaded="InitField1" /> 
<TextBlock Grid.Row="2" Grid.Column="0" Text="Field 2" /> 
<TextBox Grid.Row="2" Grid.Column="1" x:Name="field2" Text="{x:Bind ViewModel.MyModel.Field2, Mode=OneWay}" Loaded="InitField2" /> 

Zur Laufzeit, wenn ich ein nicht numerischen Zeichen in field1 eingeben, wird die Eingabe, gefiltert field1 kehrt zu seinem vorherigen Wert, ohne den Bildschirm „Blinken“ (das ist, warum ich das TextChanging Ereignis verwenden und nicht die TextChanged). Perfekt! Aber wenn ich ein numerisches Zeichen eingeben, wird field1 korrekt aktualisiert (ich, dass mit Breakpoint sehen), aber wenn field2 gesetzt ist, bekam ich eine native Ausnahme, wenn RaisedPropertyChanged genannt wird: exception

ich zu ahnen, eine Art von Threading-Fehler, aber ich bin ziemlich neu in dieser Art von Entwicklung. Irgendeine Idee? Vielen Dank!

Antwort

1

eine separate ‚Model‘ Klasse

Hier verwendet aktualisiert ist, wie Sie ein Textfeld erstellen können, dass, wenn eine Zahl (integer) hinein die eingegebene Nummer eines anderes Textfeld zeigt eingegeben wird durch eine andere Zahl multipliziert.

Hier ist die Benutzeroberfläche. Beachten Sie, dass die Mode für jede Bindung verwendet wird und die zweite Textbox schreibgeschützt ist, da dies nur zur Anzeige dient.

auf der Seite erkläre ich mein Modell

public MyViewModel ViewModel { get; set; } = new MyViewModel(); 

Mein Ansichtsmodell ist sehr einfach

public class MyViewModel 
{ 
    public MyModel MyModel { get; set; } = new MyModel(); 
} 

Die Modellklasse, die Logik

public class MyModel : INotifyPropertyChanged 
{ 
    private string _value1; 

    public string Value1 
    { 
     get { return _value1; } 
     set 
     { 
      if (_value1 != value) 
      { 
       _value1 = value; 

       // Cause the updated value to be displayed on the UI 
       OnPropertyChanged(nameof(Value1)); 

       // Is the entered value a number (int)? 
       int numericValue; 
       if (int.TryParse(value, out numericValue)) 
       { 
        // It's a number so set the other value 
        // multiplied by the ratio 
        Value2 = (numericValue * 3).ToString(); 
       } 
       else 
       { 
        // A number wasn't entered so indicate this 
        Value2 = "NaN"; 
       } 

       // Cause the updated value2 to be displayed 
       OnPropertyChanged(nameof(Value2)); 
      } 
     } 
    } 

    // We can use the automatic property here as don't need any logic 
    // relating the getting or setting this property 
    public string Value2 { get; set; } 

    public event PropertyChangedEventHandler PropertyChanged; 

    protected virtual void OnPropertyChanged(string propertyName) 
    { 
     PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 
    } 
} 

Mit dem oben enthält, wenn für Value1 t eine Zahl eingegeben wird henne Wert2 zeigt eine Zahl dreimal so viel (weil ich das Verhältnis von 3 eingestellt habe).

Sie können feststellen, dass wenn Sie das Obige versuchen, dass die Änderung nicht sofort auftritt, und Wert2 nur aktualisiert wird, wenn der Fokus das Textfeld Wert1 verlässt. Dies liegt daran, dass die bidirektionale Bindung standardmäßig nur aktualisiert wird, wenn der Fokus verloren geht. Dies kann jedoch leicht geändert werden.

Wenn anstelle der neuen x:Bind Methode der Bindung wir die traditionelle Binding Methode verwenden, können wir zwingen, die Bindung aktualisiert werden, wann immer wir wollen. Sprich, wenn der Text geändert wird.

die TextBox Erklärung wie folgt ändern:

 <TextBox Text="{Binding ViewModel.Value1, Mode=TwoWay}" 
       TextChanged="TextBox_OnTextChanged" /> 

Beachten Sie, dass die verbindliche Syntax unterschiedlich ist, und wir haben ein Ereignis hinzugefügt.
Der Handler der Veranstaltung ist

private void TextBox_OnTextChanged(object sender, TextChangedEventArgs e) 
{ 
    var be = (sender as TextBox).GetBindingExpression(TextBox.TextProperty); 
    be.UpdateSource(); 
} 

Dies zwingt die aktualisieren Bindung, aber es ist eine andere Änderung, die wir auch machen müssen.
Mit der x:Bind Syntax versucht es, an die Seite zu binden. Mit der älteren Binding Syntax bindet es sich an den DataContext der Seite. Um diese gleich zu machen, aktualisieren Sie die Seite Konstruktor wie diese

public MainPage() 
{ 
    this.InitializeComponent(); 
    this.DataContext = this; 
} 

Nun ist die App funktioniert wieder und Wert2 wird nach jeder Tastenbetätigung im Value1 Textfeld aktualisiert werden.

+0

Danke für die ausführliche Antwort; Ich werde Schritt für Schritt gehen, wenn es dich nicht stört: Ich habe das "klassische" 'Binding' ausprobiert und es funktioniert ganz gut. Für das 'x: Bind 'verstehe ich, was Sie im ViewModel gemacht haben, aber das bedeutet, dass mein Modell" weg "ist? Ist es möglich, "Value1" und "Value2" in ein Modell zu setzen und es so zu machen? Für mich ist es mein Modell, das sagt "hey, Value2 muss immer Value1 * 3 sein", also ist es ein wenig verwirrend, diese Logik im ViewModel zu sehen. Ist das klar? – benichka

+0

@benichka aktualisiert, um die Model-Klasse wie gewünscht zu trennen. –

+0

Vielen Dank für die zusätzlichen Details, ich werde es versuchen. Dennoch bin ich immer noch ein wenig verwirrt, dass in meinem Modell meine Felder "string" und nicht "int" sind. Ich verstehe die MVVM wahrscheinlich noch nicht sehr gut, aber ich dachte, dass Modelle von der Sichtweise unabhängig sein müssen. Und in diesem Szenario ist dies nicht der Fall (wir haben es mit 'string' anstatt mit' int' zu tun, um eine 'argumentException' zu verhindern). Ich habe eine andere Frage gestellt, um dieses Problem zu umgehen. anscheinend ist eine der wenigen Möglichkeiten, einen Konverter zu verwenden. – benichka

Verwandte Themen