CommunityToolkit / dotnet

.NET Community Toolkit is a collection of helpers and APIs that work for all .NET developers and are agnostic of any specific UI platform. The toolkit is maintained and published by Microsoft, and part of the .NET Foundation.
https://docs.microsoft.com/dotnet/communitytoolkit/?WT.mc_id=dotnet-0000-bramin
Other
2.99k stars 294 forks source link

Add some kind of [TrySetProperty] attribute #753

Open SKSniperSK opened 1 year ago

SKSniperSK commented 1 year ago

Overview

The problem I would like to solve is: Don't change a value when it fails validation but instead invoke the ErrorsChanged event, all of this just with attributes / code generators.

More in detail: As soon as you don't want invalid values to be saved you can no longer work with simple attributes but have to use TrySetProperty. But TrySetProperty is missing an overload to invoke the ErrorsChanged event but only relies on the out parameter to deliver validation errors. Therefore any event based validation handling stops working and needs some kind of workaround (like firing additional OnPropertyChanged calls to process the validation errors or to manually call ValidateProperty again just so that the ErrorsChanged event gets invoked.

API breakdown

namespace CommunityToolkit.Mvvm.ComponentModel;

[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
public sealed class TrySetProperty : Attribute
{
}

Usage example

[ObervableObject]
[TrySetProperty]
[MaxLength(20)]
private string name;

which would generate something like

private string name;

[MaxLength(20)]
public string Name
{
    get => name;
    set => TrySetProperty(ref name, value); // <- using a new overload that works with ErrorsChanged the same way as SetProperty(ref name, value, true) does.

Breaking change?

I'm not sure

Alternatives

Currently I only see one workaround if event based validation handling is used, doing an additional, unneeded validation just to trigger the event:

private string name;

[MaxLength(20)]
public string Name
{
    get => name;
    set
    {
        if (!TrySetProperty(ref name, value, out _)) ValidateProperty(nameof(Name));
    }

Every other workaround ignores events and just processes the provided IReadOnlyCollection<ValidationResult> out variable.

Additional context

No response

Help us help you

No, just wanted to propose this

mikechristiansenvae commented 11 months ago

There's a problem with this, however. You're going to run into issues with FrameworkCompatibilityPreferences.KeepTextBoxDisplaySynchronizedWithTextProperty (docs)

This is commonly seen in WPF when binding a TextBox to a double (for example, see this Stack Overflow post)

Suppose:

The user types a 1 in the TextBox.

In cases like these, your view model should represent the "working state", not the final "approved state".