UWP:在设置另一个属性后设置属性时的异常

时间:2017-03-02 17:44:31

标签: windows xaml exception uwp inotifypropertychanged

我认为我的UWP应用中出现了线程问题。 我想做一件非常简单的事情:

  • 包含2个数字字段的用户界面;
  • 如果在field1中输入了数字值,我希望field2的比率设置为field1(例如:field2 = ratio * field1)。

我正在使用x:BindTextChanging个事件。由于未知原因,我无法在XAML中“调用”TextChanging事件而不会在启动时出现异常。因此,我正在使用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));
    }
}

我的代码落后(我正在过滤输入以避免强制转换异常):

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将返回其先前的值而屏幕不会闪烁(这就是我使用{{1}的原因事件而不是TextChanging)。完善!但是如果我键入一个数字字符,TextChanged被正确更新(我可以看到断点),但是当设置field1时,我在调用field2时得到了一个本机异常: exception

我怀疑某种线程错误,但我对这种开发很陌生。任何的想法?谢谢!

1 个答案:

答案 0 :(得分:1)

更新后使用单独的“模型”类

以下是如何创建一个文本框,当输入一个数字(整数)时,另一个文本框显示输入的数字乘以另一个数字。

这是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();
}

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文本框中每次按键后都会更新Value2。