我想我的UWP应用程序中有一个线程问题。 我想做一个非常简单的事情:
field1
键入数字值,我希望field2
被设置为field1
的比例(例如: field2 = ratio * field1
)。 我正在使用x:Bind
和TextChanging
事件。 由于未知的原因,我无法在XAML中“调用” TextChanging
事件,而不会在启动时发生exception。 因此,我正在使用Loaded
事件。
这是我的模型类,简称为MyModel
:
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; } } }
我的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)); } }
我的代码背后(我正在筛选input,以避免转换exception):
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); } } }
最后,我的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" />
在运行时,如果我在字段1中键入一个非数字字符,input被过滤,字段1返回到它的以前的值没有屏幕“闪烁”(这就是为什么我使用TextChanging
事件,而不是TextChanged
)。 完善! 但是,如果我键入一个数字字符, field1
正确更新(我可以看到与断点),但是当field2
设置时,我调用RaisedPropertyChanged
时得到本机exception:
我怀疑某种线程错误,但是我对这种开发很新颖。 任何想法? 谢谢!
更新为使用单独的“模型”类
以下是如何创建一个文本框,当输入数字(整数)时,另一个文本框显示输入的数字乘以另一个数字。
这是用户界面。 注意每个绑定使用的Mode
,第二个文本框是只读的,因为这只是为了显示。
<StackPanel> <TextBlock Text="Value 1" /> <TextBox Text="{x:Bind ViewModel.MyModel.Value1, Mode=TwoWay}" /> <TextBlock Text="Value 2" /> <TextBox Text="{x:Bind ViewModel.MyModel.Value2, Mode=OneWay}" IsReadOnly="True" /> </StackPanel>
在我宣布我的模型的页面上
public MyViewModel ViewModel { get; set; } = new MyViewModel();
我的ViewModel非常简单
public class MyViewModel { public MyModel MyModel { get; set; } = new MyModel(); }
Model类包含逻辑
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)); } }
如上所述,当为Value1输入一个数字时,Value2将显示一个数字的三倍(因为我已经设置了比率3)。
您可能会注意到,如果您尝试上述更改不会立即发生,并且Value2仅在焦点离开Value1文本框时更新。 这是因为,默认情况下,双向绑定仅在焦点丢失时才会更新。 这可以很容易地改变。
如果不是使用新的x:Bind
方法,而是使用传统的Binding
方法,我们可以强制绑定被更新,只要我们想要的。 说,当文字被改变。
像这样修改TextBox声明:
<TextBox Text="{Binding ViewModel.Value1, Mode=TwoWay}" TextChanged="TextBox_OnTextChanged" />
请注意,绑定语法是不同的,我们已经添加了一个事件。
事件的处理程序是
private void TextBox_OnTextChanged(object sender, TextChangedEventArgs e) { var be = (sender as TextBox).GetBindingExpression(TextBox.TextProperty); be.UpdateSource(); }
这迫使绑定更新,但还有一个变化,我们必须做出。
使用x:Bind
语法尝试绑定到页面。 使用较老的Binding
语法,它绑定到页面的DataContext。 为了使这些相同,像这样更新页面构造器
public MainPage() { this.InitializeComponent(); this.DataContext = this; }
现在,应用程序将再次运行,Value1将在每次按下Value1文本框中的按键后进行更新。