3

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?

IggyBlob
  • 372
  • 2
  • 13

2 Answers2

3

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.

MelloPs
  • 135
  • 11
  • 1
    Thanks for reminding about [`Binding.UpdateSourceTrigger`](https://learn.microsoft.com/en-us/uwp/api/windows.ui.xaml.data.binding.updatesourcetrigger) it is a useful feature to keep in mind dealing with UWP `TextBox` and other text input controls, but in this particular question test scenario it doesn't help. – DK. Dec 12 '18 at 09:42
  • 1
    @DK is right. `UpdateSourceTrigger=PropertyChanged` does not make any difference in this specific case. – IggyBlob Dec 12 '18 at 11:56
  • @DK okay sorry than I think sometimes binding does make it more complicated then it could/should be. When my binding sometimes does not get updated correctly I try to update it on the UIThread which mostly work maybe this helps. Maybe there is no difference just bad timing this happens a lot when I work with async methods and multple updates of the UI – MelloPs Dec 12 '18 at 14:27
2

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.

DK.
  • 3,173
  • 24
  • 33
  • So basically, this strange behaviour occurs due to different implementations of `Binding` and `x:Bind`? – IggyBlob Dec 12 '18 at 14:11
  • @IggyBlob yes, my gut feeling is that this is how it is internally designed; e.g. if `Binding` sets source property value - it expects `PropertyChanged` event to go off because of that, ignores it and doesn't verify afterwards if property value is matching what `TextBox` displays, while `x:Bind` internals being more cautious. All these implementation details are my speculation of course, but it's certain that `Binding` and `x:Bind` have to have different implementation and your tests show where exactly they behave differently. – DK. Dec 13 '18 at 01:51