UWP:设置 属性 后设置另一个异常
UWP: exception when setting a property just after setting another one
我想我的 UWP 应用程序有线程问题。
我想做一件很简单的事:
- a UI 有 2 个数字字段;
- 如果在
field1
中输入数值,我希望field2
设置为field1
的比率(例如:field2 = ratio * field1
)。
我正在使用 x:Bind
和 TextChanging
事件。由于未知原因,我无法在 XAML 到 "call" 事件中 TextChanging
事件在启动时没有异常。因此,我正在使用 Loaded
事件。
这是我的模型 class,简称为 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;
}
}
}
我的视图模型:
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));
}
}
我的代码隐藏(我正在过滤输入以避免强制转换异常):
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" />
在运行时,如果我在 field1
中键入一个非数字字符,输入将被过滤,field1
returns 到它以前的值,没有屏幕 "blinking" (这就是为什么我使用 TextChanging
事件而不是 TextChanged
)。完美的!但是如果我输入一个数字字符,field1
会正确更新(我可以通过断点看到),但是当设置 field2
时,调用 RaisedPropertyChanged
时我会遇到本机异常:
我怀疑是某种线程错误,但我对这种开发还很陌生。任何的想法?谢谢!
已更新为使用单独的 'Model' class
下面介绍了如何创建一个文本框,当输入一个数字(整数)时,另一个文本框会显示输入的数字乘以另一个数字。
这是 UI。请注意用于每个绑定的 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();
}
模型class包含逻辑
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)。
您可能会注意到,如果尝试上述操作,更改不会立即发生,并且只有当焦点离开 Value1 文本框时,Value2 才会更新。这是因为,默认情况下,双向绑定仅在失去焦点时更新。不过这很容易更改。
如果我们不使用新的 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 文本框中按下按键后,Value2 都会更新。
我想我的 UWP 应用程序有线程问题。 我想做一件很简单的事:
- a UI 有 2 个数字字段;
- 如果在
field1
中输入数值,我希望field2
设置为field1
的比率(例如:field2 = ratio * field1
)。
我正在使用 x:Bind
和 TextChanging
事件。由于未知原因,我无法在 XAML 到 "call" 事件中 TextChanging
事件在启动时没有异常。因此,我正在使用 Loaded
事件。
这是我的模型 class,简称为 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;
}
}
}
我的视图模型:
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));
}
}
我的代码隐藏(我正在过滤输入以避免强制转换异常):
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" />
在运行时,如果我在 field1
中键入一个非数字字符,输入将被过滤,field1
returns 到它以前的值,没有屏幕 "blinking" (这就是为什么我使用 TextChanging
事件而不是 TextChanged
)。完美的!但是如果我输入一个数字字符,field1
会正确更新(我可以通过断点看到),但是当设置 field2
时,调用 RaisedPropertyChanged
时我会遇到本机异常:
我怀疑是某种线程错误,但我对这种开发还很陌生。任何的想法?谢谢!
已更新为使用单独的 'Model' class
下面介绍了如何创建一个文本框,当输入一个数字(整数)时,另一个文本框会显示输入的数字乘以另一个数字。
这是 UI。请注意用于每个绑定的 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();
}
模型class包含逻辑
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)。
您可能会注意到,如果尝试上述操作,更改不会立即发生,并且只有当焦点离开 Value1 文本框时,Value2 才会更新。这是因为,默认情况下,双向绑定仅在失去焦点时更新。不过这很容易更改。
如果我们不使用新的 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 文本框中按下按键后,Value2 都会更新。