Incorrect XAML codegen for INotifyDataErrorInfo
See original GitHub issueDiscovered in https://github.com/windows-toolkit/WindowsCommunityToolkit/issues/3569
Describe the bug
It looks like that the XAML codegen when binding a TextBox
to a property from a viewmodel implementing INotifyDataErrorInfo
is incorrect, which causes the UI not to be properly updated then an invalid property is toggled back to valid, if this is the last invalid property in the viewmodel. Toggling other properties seems to work just fine instead.
Steps to reproduce the bug
Steps to reproduce the behavior:
- Clone https://github.com/mvegaca/WindowsTemplateStudio/tree/Mockup-WinUI3-ObservableValidator
- Open the app, go do the Forms WCT page
- Click “Submit”, all fields will be invalid
- Start filling in fields with valid values
- Observe that whatever field you leave for last, it’s impossible to make it valid again
Expected behavior
Fields should all be updated correctly in the UI.
Screenshots
Version Info
NuGet package version:
Windows app type:
UWP | Win32 |
---|---|
Yes |
Windows 10 version | Saw the problem? |
---|---|
Insider Build (xxxxx) | |
May 2020 Update (19041) | Yes |
November 2019 Update (18363) | |
May 2019 Update (18362) | |
October 2018 Update (17763) | |
April 2018 Update (17134) | |
Fall Creators Update (16299) | |
Creators Update (15063) |
Device form factor | Saw the problem? |
---|---|
Desktop | Yes |
Xbox | |
Surface Hub | |
IoT |
Additional context
Here is my analysis of the issue, from https://github.com/windows-toolkit/WindowsCommunityToolkit/issues/3569#issuecomment-772555360.
I’m looking at the generated .g.cs
backing file and it seems to me there might be something off here.
The registration of INotifyDataErrorInfo
itself is done correctly right next to INotifyPropertyChanged
:
public void UpdateChildListeners_ViewModel(global::WinUIDesktopApp.ViewModels.FormWCTViewModel obj)
{
if (obj != cache_ViewModel)
{
// ...
if (obj != null)
{
cache_ViewModel = obj;
((global::System.ComponentModel.INotifyPropertyChanged)obj).PropertyChanged += PropertyChanged_ViewModel;
((global::System.ComponentModel.INotifyDataErrorInfo)cache_ViewModel).ErrorsChanged += ErrorsChanged_ViewModel;
}
}
}
Now, consider the situation where the bug happens, so when we have a viewmodel with just one remaining field that is then updated, validated again and then marked as valid. The viewmodel now has 0 errors, and it raises ErrorsChanged
to notify the UI of this change in errors. We expect the view to clear the errors in that control bound to this property, which is not happening. This is the code that is invoked when ErrorsChanged
is raised:
public void ErrorsChanged_ViewModel(object sender, global::System.ComponentModel.DataErrorsChangedEventArgs e)
{
FormWCTPage_obj1_Bindings bindings = TryGetBindingObject();
if (bindings != null)
{
string propName = e.PropertyName;
if (global::System.String.IsNullOrEmpty(propName))
{
bindings.UpdateErrors_ViewModel((global::System.ComponentModel.INotifyDataErrorInfo)sender, "OrderID");
// All other properties here...
}
else
{
bindings.UpdateErrors_ViewModel((global::System.ComponentModel.INotifyDataErrorInfo)sender, propName);
}
}
}
In this case propName
is "ShipsTo"
, so the second branch is taken - we can move on to UpdateErrors_ViewModel
:
private void UpdateErrors_ViewModel(global::System.ComponentModel.INotifyDataErrorInfo sender, string propertyName)
{
if (this.initialized)
{
switch (propertyName)
{
// All other properties here...
case "ShipTo":
{
UpdateErrors_(obj6, sender, "ShipTo");
break;
}
}
}
}
Just a wrapper to actually switch the bound property name and the respecting control, this is fine. Now on to the possibly faulty method:
private void UpdateErrors_(global::Microsoft.UI.Xaml.Controls.Control control, global::System.ComponentModel.INotifyDataErrorInfo sender, string propertyName)
{
if (sender.HasErrors)
{
UpdateInputValidationErrors(control, sender.GetErrors(propertyName));
}
}
But here, sender.HasErrors
is false
now, because we just validated the last incorrect property. So this method just returns and the UI is not updated at all, which explains why we’re seeing that behavior in the last field being modified.
This is what makes me say it looks like a XAML codegen issue. It seems to me that there should at least be another branch in UpdateErrors_
that just clears all the errors in the target control without doing other checks in case the linked viewmodel has no errors left anymore.
Issue Analytics
- State:
- Created 3 years ago
- Reactions:1
- Comments:15 (11 by maintainers)
Top GitHub Comments
Gotcha, thanks for your patience, I’m finally there 😃
I agree that this
if (sender.HasErrors)
check could just be removed. Removing the check would be my vote, because theUpdateInputValidationErrors
method will already do the right thing.Sorry for the delayed response on this (and thanks @azchohfi for pinging me!) , but the fix is checked in for the upcoming Reunion 1.0 fall release.