3
votes

I am developing an UWP app leveraging the MVVM paradigm. My view contains a simple TextBox that has its Text property bound to a respective ViewModel property:

<TextBox Text="{Binding Path=Radius, Mode=TwoWay}"/>

Naturally, I've assigned my ViewModel to the page's DataContext:

public sealed partial class ExamplePage : Page
{
    private ExamplePageVM viewModel;

    public ExamplePage()
    {
        this.InitializeComponent();
        viewModel = new ExamplePageVM();
        DataContext = viewModel;
    }
}

In the ViewModel I perform some kind of input validation, i. e. if the user inserts an invalid float value into the TextBox I want to reset the TextBox to a default value (zero, for instance):

class ExamplePageVM : INotifyPropertyChanged 
{
    public event PropertyChangedEventHandler PropertyChanged;
    private float radius;

    public string Radius
    {
        get => radius.ToString();
        set
        {
            if (radius.ToString() != value)
            {
                if (!float.TryParse(value, out radius)) radius = 0;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Radius)));
            }
        }
    }
}

Changing the value in the TextBox causes the setter to be called as intended. Also, the PropertyChanged event is invoked accordingly. However, the TextBox still contains invalid data after the setter execution has finished, which means that the view isn't updated correctly.

According to the first comment on this post, the solution to this issue is using <TextBox Text="{x:Bind viewModel.Radius, Mode=TwoWay}"/> instead of the Binding approach shown above. Why is that so? What's the difference between Binding and x:Bind in this very situation?

2

2 Answers

2
votes

Binding to TextBox.Text is a rather special case, because Microsoft made a decision where the most common scenario is that the binding should be updated when control loses focus, as opposed to each and every input text change. This allows 2 things:

  • somewhat more efficient handling of larger texts
  • safeguarding user input in progress from being changed by application

In the absence of publicly available UWP source code it's possible that MS developers may provide you with more reliable insight, but even comparing the changes to a bound source with direct tracking of the EditBox.TextProperty via DependencyObject.RegisterPropertyChangedCallback makes you expect that instead of the usual direct binding to the dependency property changes there is actually an additional man-in-the-middle kind of implementation in TextBox that handles how and when TextProperty updates affect and being affected by the DataContext or underlying class properties bound with {Binding} or {x:Bind}.

Note that {x:Bind} and {Binding} are very different mechanisms, especially with the first being a compile-time and the 2nd is run-time, meaning that internally they require different implementation and it's up to the framework developers to make sure they exhibit the identical behavior.

Now in your test scenario you are trying to validate and possibly change a property value in the bound data source expecting that the TextBox will display the value you want, which it does with {x:Bind}, but not with {Binding}.

Apparently you've found a scenario where {x:Bind} and {Binding} implementations behave differently. I did the same tests and totally confirm your findings.

2
votes

You may want to set the UpdateTrigger yourself since TextBox normally updates the source when focus lost gets called.

You can change the behaviour UpdateSourceTrigger=PropertyChanged.

<TextBox Text="{x:Bind AnswerText, UpdateSourceTrigger=PropertyChanged}"/>

<TextBox Text="{Binding AnswerText, UpdateSourceTrigger=PropertyChanged}"/>

If this is not working you may want to prevent inputs different then numbers with the keydown event. Which you could outsource in a user control for reuse.

Hope this helps.