New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Incorrect XAML codegen for INotifyDataErrorInfo #4092
Comments
@Sergio0694 is this issue in Winui2 and/or Winui3? |
@StephenLPeters The repro that was provided to me from the Windows Template Studio (linked in my post above) is using a packaged Win32 WinUI 3 app. Haven't tried to repro this with UWP WinUI 2 yet since I know there's no official support for |
@MikeHillberg or @Austin-Lamb what is the state of data validation in winui3? |
FYI @stevenbrix as well. I know the Input Validation is still listed as 'preview' on the new roadmap for WinUI 3, but if this is indeed a simple bug fix, it'd be great to see if the generation could be patched. Windows Template Studio is trying to show this as an example in their WinUI templates on top of our new .NET Standard based MVVM Toolkit in the Windows Community Toolkit; so it'd be great if we'd have that working as intended and be able to gather more feedback from the community for this feature as it moves through it's preview phase. 🙂 |
To prove this isn't an issue with WIindows Community Toolkit, or Martin's Template Studio code, here's a minimal repro which uses neither: Using
Steps to reproduce the bug
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBox Header="First Name"
MinWidth="200"
Text="{x:Bind ViewModel.FirstName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Header="Last Name"
MinWidth="200"
Text="{x:Bind ViewModel.LastName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
public sealed partial class MainPage : Page
{
public MainViewModel ViewModel { get; set; }
public MainPage()
{
ViewModel = new MainViewModel();
this.InitializeComponent();
}
}
public class MainViewModel : INotifyPropertyChanged, INotifyDataErrorInfo
{
private string firstName;
private string lastName;
public string FirstName
{
get => firstName;
set
{
firstName = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(FirstName)));
ErrorsChanged(this, new DataErrorsChangedEventArgs(nameof(LastName)));
}
}
public string LastName
{
get => lastName;
set
{
lastName = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(LastName)));
ErrorsChanged(this, new DataErrorsChangedEventArgs(nameof(LastName)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public IEnumerable<object> GetErrors(string propertyName)
{
switch (propertyName)
{
case "FirstName":
if (string.IsNullOrWhiteSpace(FirstName))
{
yield return "FirstName cannot be blank";
}
break;
case "LastName":
if (string.IsNullOrWhiteSpace(LastName))
{
yield return "LastName cannot be blank";
}
break;
default:
break;
}
}
public bool HasErrors => string.IsNullOrWhiteSpace(FirstName) || string.IsNullOrWhiteSpace(LastName);
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
}
(Only tested with UWP on Desktop) |
Actually, (thanks to the power of a minimal repro,) I can confirm that the same behavior is seen in a WinUI for Desktop app. The above repro needs to following modifications for Desktop:
|
I'm also hitting this. I used my custom implementation and implementation in WCTK to prove it isn't implementation issue of |
Thanks all for the input here. IIRC, we took the design based off the way the WPF Binding engine would interact with the INDEI. The Binding code doesn't have intelligence to know which errors were removed, so the source of the errors needs to clear them every time before running validation logic. @mrlacey I don't see the implementation of the |
Also having this issue :( |
Hey @stevenbrix, can you clarify on what you mean here?
As in, considering that types implementing I guess I'm confused because from what I can see here, regardless of the reason why the current implementation of the XAML compiler ended up being what we have today, the final codegen just seems incorrect to me. As in, to recap, we have this: 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));
}
} Which is generated for all individual controls that bind to a property from a viewmodel with Just as an example, either of these two options would fix the issue already: // Option 1, just remove the branch entirely. When the property no longer
// has errors, the control just receives an empty list of errors and refreshes.
UpdateInputValidationErrors(control, sender.GetErrors(propertyName)); OR: if (sender.HasErrors)
{
UpdateInputValidationErrors(control, sender.GetErrors(propertyName));
}
else
{
// Option 2, fast path to just clear all validation errors from a control
ClearInputValidationErrors(control);
} Thoughts? |
Gotcha, thanks for your patience, I'm finally there :) I agree that this |
Just closing the loop here. Yes, I see the same in the generated code with my repro.
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));
}
} which is the same as Sergio showed above |
@stevenbrix Awesome, glad we're all on the same page now 😄 Removing that |
FYI @MikeHillberg, this should be a simple fix to enable customers to provide feedback on this feature; otherwise it's broken. |
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. |
Discovered 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 implementingINotifyDataErrorInfo
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:
Expected behavior
Fields should all be updated correctly in the UI.
Screenshots
Version Info
NuGet package version:
Windows app type:
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 toINotifyPropertyChanged
: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 whenErrorsChanged
is raised:In this case
propName
is"ShipsTo"
, so the second branch is taken - we can move on toUpdateErrors_ViewModel
:Just a wrapper to actually switch the bound property name and the respecting control, this is fine.
Now on to the possibly faulty method:
But here,
sender.HasErrors
isfalse
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.The text was updated successfully, but these errors were encountered: