Search code examples
c#mauimaui-community-toolkit

net maui entry Behaviors and Triggers on validation not triggers


I stuck with the validation process. On text change, the view model property (Name, the only one on what I am trying to achieve this) gets the value changed.

I am using CommunityToolkit.Mvvm v8.0.0.

My vie model looks like this:

public partial class PlayerModel : ObservableValidator
{
    private int id;
    private string name;
    private string localImageLink;
    private string webImageLink;
    private string club;
    private string birthday;
    private string birthPlace;
    private int weight;
    private double height;
    private string positionName;
    private int positionId;
    private string description;

    public ICommand ValidateCommand => new RelayCommand(() => ValidateAllProperties());

    public int Id
    {
        get => this.id;
        set => SetProperty(ref this.id, value, true);
    }

    [Required]
    [StringLength(255)]
    [MinLength(2)]
    public string Name
    {
        get => this.name;
        set
        {
            ClearErrors();
            ValidateAllProperties();

            SetProperty(ref this.name, value);

            _ = this[nameof(Name)];
        }
    }

    [StringLength(4096)]
    public string LocalImageLink
    {
        get => this.localImageLink;
        set => SetProperty(ref this.localImageLink, value, true);
    }

    [Required]
    [StringLength(4096)]
    public string WebImageLink
    {
        get => this.webImageLink;
        set => SetProperty(ref this.webImageLink, value, true);
    }

    [Required]
    [StringLength(255)]
    public string Club
    {
        get => this.club;
        set => SetProperty(ref this.club, value, true);
    }

    [Required]
    [StringLength(32)]
    public string Birthday
    {
        get => this.birthday;
        set => SetProperty(ref this.birthday, value, true);
    }

    [Required]
    [StringLength(255)]
    public string BirthPlace
    {
        get => this.birthPlace;
        set => SetProperty(ref this.birthPlace, value, true);
    }

    [Required]
    [Range(0, 100)]
    public int Weight
    {
        get => this.weight;
        set => SetProperty(ref this.weight, value, true);
    }

    [Required]
    [Range(0, 2.5)]
    public double Height
    {
        get => this.height;
        set => SetProperty(ref this.height, value, true);
    }

    [Required]
    public string Description
    {
        get => this.description;
        set => SetProperty(ref this.description, value, true);
    }

    public string PositionName
    {
        get => this.positionName;
        set => SetProperty(ref this.positionName, value, true);
    }

    [Required]
    [Range(1, 7)]
    public int PositionId
    {
        get => this.positionId;
        set => SetProperty(ref this.positionId, value, true);
    }

    public ValidationStatus this[string propertyName]
    {
        get
        {
            var errors = this.GetErrors()
                             .ToDictionary(k => k.MemberNames.First(), v => v.ErrorMessage) ?? new Dictionary<string, string?>();

            var hasErrors = errors.TryGetValue(propertyName, out var error);
            return new ValidationStatus(hasErrors, error ?? string.Empty);
        }
    }
}

public class ValidationStatus: ObservableObject
{
    private bool hasError;
    private string error;

    public bool HasError
    {
        get => this.hasError;
        set => SetProperty(ref this.hasError, value);
    }

    public string Error
    {
        get => this.error;
        set => SetProperty(ref this.error, value);
    }

    public ValidationStatus()
    {
    }

    public ValidationStatus(bool hasError, string error)
    {
        this.hasError = hasError;
        this.error = error;
    }
}

I got stuck when I need to display a validation error (make the entry red, and show a label with a red text, for example). The entry field are always red, and the error message is never shown. When I debug public ValidationStatus this[string propertyName] get callaed, but I am not shore is it reflected back, or I am doing something wrong.

Part of my view:

VerticalStackLayout>
    <Label Text="Name" />
    <Entry x:Name="name" Text="{Binding Name, Mode=TwoWay}"
           ClearButtonVisibility="WhileEditing">
        <Entry.Behaviors>
            <toolkit:EventToCommandBehavior EventName="TextChanged"
                                            Command="{Binding ValidateCommand}" />
        </Entry.Behaviors>
        <Entry.Triggers>
            <DataTrigger TargetType="Entry"
                         Binding="{Binding [Name].HasError}"
                         Value="True">
                <Setter Property="BackgroundColor" Value="red" />
            </DataTrigger>
        </Entry.Triggers>
    </Entry>
    <Label Text="{Binding [Name].Error}"
           TextColor="red" />
</VerticalStackLayout>

If anyone done something like this, or could point me on the right directions. Another question is that I left from my view the following code, that is suggested in the official docks:

<Entry.Style>
  <OnPlatform x:TypeArguments="Style">
       <On Platform="iOS, Android" Value="{StaticResource EntryStyle}" />
       <On Platform="WinUI" Value="{StaticResource WinUIEntryStyle}" />
   </OnPlatform>
</Entry.Style>

Do I need them? What it's function?

thnx


Solution

  • It's about how to raise propertychanged for an indexer. I made a demo using two Properties in ViewModel. You could try the following code:

    In .xaml, not necessary to use behaviors

    <ScrollView>
        <VerticalStackLayout>
            <VerticalStackLayout>
                <Label Text="Name" />
                <Entry x:Name="name" Text="{Binding Name,Mode=TwoWay}"
                   ClearButtonVisibility="WhileEditing">
                <Entry.Triggers>
                    <DataTrigger TargetType="Entry"
                                 Binding="{Binding [Name].HasError}"
                                 Value="True">
                        <Setter Property="BackgroundColor" Value="Red" />
                    </DataTrigger>
                </Entry.Triggers>
                </Entry>
                <Label  BackgroundColor="Yellow" Text="{Binding [Name].Error}"
                   TextColor="Red" />
            </VerticalStackLayout>
            <VerticalStackLayout >
                <Label Text="Club" />
                <Entry x:Name="club" Text="{Binding Club,Mode=TwoWay}" ClearButtonVisibility="WhileEditing">
                <Entry.Triggers>
                    <DataTrigger TargetType="Entry"
                                 Binding="{Binding [Club].HasError}"
                                 Value="True">
                        <Setter Property="BackgroundColor" Value="Red" />
                    </DataTrigger>
                </Entry.Triggers>
                </Entry>
                <Label  BackgroundColor="Yellow" Text="{Binding [Club].Error}"
                   TextColor="Red" />
            </VerticalStackLayout>
        </VerticalStackLayout>
    </ScrollView>
    

    In ViewModel, the most important is to Raise PropertyChanged for Indexer. That is OnPropertyChanged["Item[key]"].

    public class MainPageViewModel : ObservableValidator
    {
    
        private string name;
        private string club;
    
        [Required]
        [StringLength(255)]
        [MinLength(2)]
        public string Name
        {
            get => this.name;
            set
            {
                ClearErrors();              
                SetProperty(ref this.name, value);
                ValidateAllProperties();
                OnPropertyChanged("Item[Name]");
            }
        }
    
        [Required]
        [StringLength(255)]
        [MinLength(2)]
        public string Club
        {
            get => this.club;
            set
            {
                ClearErrors();
                SetProperty(ref this.club, value);
                ValidateAllProperties();
                OnPropertyChanged("Item[Club]");
            }
        }
    
        [IndexerName ("Item")]
        public ValidationStatus this[string propertyName]
        {
            get
            {
                var errors = this.GetErrors()
                                 .ToDictionary(k => k.MemberNames.First(), v => v.ErrorMessage) ?? new Dictionary<string, string?>();
    
                var hasErrors = errors.TryGetValue(propertyName, out var error);
                return new ValidationStatus(hasErrors, error ?? string.Empty);
            }
        }
    }
    

    Some useful info you could refer to:

    PropertyChanged for indexer property

    How do I use XAML to bind to a property inside a class instance in the view-model?

    ObservableObject.OnPropertyChanged Method

    Hope it works for you.