0

Still learning. I am trying to do a model validation using CommunityToolkit.Mvvm and I stuck in the styles (min there).

Here is what i tried:

Model:

public abstract partial class BaseModel : ObservableObject, IDataErrorInfo
{
    private Dictionary<string, string> validationErrors = new Dictionary<string, string>();
    private bool isValid = false;

    protected Dictionary<string, string> ValidationErrors
    {
        get => validationErrors;
        private set => SetProperty(ref validationErrors, value);
    }

    protected bool IsValid
    {
        get => isValid;
        private set => SetProperty(ref isValid, value);
    }

    [RelayCommand]
    protected void Validate()
    {
        var context = new ValidationContext(this);
        var errors = new List<ValidationResult>();

        isValid = Validator.TryValidateObject(this, context, errors, true);
        foreach (var error in errors)
        {
            var columnName = error.MemberNames.First();

            if(!validationErrors.ContainsKey(columnName))
                validationErrors.Add(columnName, error.ErrorMessage);
        }
    }


    // check for general model error
    public string Error { get; private set; } = null;

    // check for property errors
    public string this[string columnName]
    {
        get
        {
            Validate();
            return validationErrors[columnName];
        }
    }
}

public class PlayerModel : BaseModel
{
    public int Id { get; set; }

    [Required]
    [StringLength(255)]
    public string Name { get; set; }

    [StringLength(4096)]
    public string LocalImageLink { get; set; }

    [Required]
    [StringLength(4096)]
    public string WebImageLink { get; set; }

    [Required]
    [StringLength(255)]
    public string Club { get; set; }

    [Required]
    [StringLength(32)]
    public string Birthday { get; set; }

    [Required]
    [StringLength(255)]
    public string BirthPlace { get; set; }

    [Required]
    [Range(0, 100)]
    public int Weight { get; set; }

    [Required]
    [Range(0, 2.5)]
    public double Height { get; set; }

    [Required]
    public string Description { get; set; }

    public string PositionName { get; set; }

    [Required]
    [Range(1, 7)]
    public int PositionId { get; set; }

    public PlayerModel()
    {
    }

    public PlayerModel(int id, string name, string localImageLink, string webImageLink, string club, string birthday, string birthPlace, int weight, double height, string description, string positionName, int positionId)
    {
        Id = id;
        Name = name;
        LocalImageLink = localImageLink;
        WebImageLink = webImageLink;
        Club = club;
        Birthday = birthday;
        BirthPlace = birthPlace;
        Weight = weight;
        Height = height;
        Description = description;
        PositionName = positionName;
        PositionId = positionId;
    }

    public PlayerModel(int id, string name, string localImageLink, string webImageLink, string club, string birthday, string birthPlace, int weight, double height, string description, PositionModel position)
    {
        Id = id;
        Name = name;
        LocalImageLink = localImageLink;
        WebImageLink = webImageLink;
        Club = club;
        Birthday = birthday;
        BirthPlace = birthPlace;
        Weight = weight;
        Height = height;
        Description = description;
        PositionName = position.Name;
        PositionId = position.Id;
    }

    public PlayerModel(PlayerEntity player)
    {
        Id = player.Id;
        Name = player.Name;
        LocalImageLink = player.LocalImageLink;
        WebImageLink = player.WebImageLink;
        Club = player.Club;
        Birthday = player.Birthday;
        BirthPlace = player.BirthPlace;
        Weight = player.Weight;
        Height = player.Height;
        Description = player.Description;
        PositionName = player.Position.Name;
        PositionId = player.Position.Id;
    }

    public PlayerEntity ToEntity()
    {
        return new PlayerEntity
        {
            Id = Id,
            Name = Name,
            LocalImageLink = LocalImageLink,
            WebImageLink = WebImageLink,
            Club = Club,
            Birthday = Birthday,
            BirthPlace = BirthPlace,
            Weight = Weight,
            Height = Height,
            Description = Description,
            Position = new PositionEntity
            { 
                Id = PositionId,
                Name = PositionName
            }
        };
    }

    public void ToEntity(PlayerEntity player)
    {
        player.Id = Id;
        player.Name = Name;
        player.LocalImageLink = LocalImageLink;
        player.WebImageLink = WebImageLink;
        player.Club = Club;
        player.Birthday = Birthday;
        player.BirthPlace = BirthPlace;
        player.Weight = Weight;
        player.Height = Height;
        player.Description = Description;

        player.Position.Id = PositionId;
        player.Position.Name = PositionName;
    }
}

View:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MauiUI.Pages.AddOrUpdatePlayer"
             xmlns:local="clr-namespace:Backend.Models;assembly=Backend.Models"
             xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit">

    <ContentPage.BindingContext>
        <local:PlayerModel />
    </ContentPage.BindingContext>

    <ContentPage.ToolbarItems>
        <ToolbarItem IconImageSource="save.svg" Clicked="OnSaveClick"/>
    </ContentPage.ToolbarItems>

    <ScrollView Margin="10">
        <VerticalStackLayout>
            <VerticalStackLayout>
                <Label Text="Name" />
                <Entry x:Name="name" Text="{Binding Name, Mode=TwoWay}"
                       ClearButtonVisibility="WhileEditing">
                    <Entry.Behaviors>
                        <toolkit:EventToCommandBehavior
                                EventName="TextChanged"
                                Command="{Binding Validate}" />
                    </Entry.Behaviors>
                </Entry>
            </VerticalStackLayout>
            <VerticalStackLayout Margin="0,10">
                <Label Text="Position" />
                <Picker x:Name="position" Title="Select..."
                        ItemDisplayBinding="{Binding Name}"
                        SelectedItem="{Binding PositionId, Mode=TwoWay}" />
            </VerticalStackLayout>
            <VerticalStackLayout Margin="0,10">
                <Label Text="Club" />
                <Entry x:Name="club" Text="{Binding Club, Mode=TwoWay}" 
                       ClearButtonVisibility="WhileEditing" />
            </VerticalStackLayout>
            <VerticalStackLayout Margin="0,10">
                <Label Text="Birthday" />
                <DatePicker  x:Name="birthday" Date="{Binding Birthday, Mode=TwoWay}" />
            </VerticalStackLayout>
            <VerticalStackLayout Margin="0,10">
                <Label Text="Birth place" />
                <Entry x:Name="birthplace" Text="{Binding BirthPlace, Mode=TwoWay}" 
                       ClearButtonVisibility="WhileEditing" />
            </VerticalStackLayout>
            <VerticalStackLayout Margin="0,10">
                <Label Text="Weight" />
                <Entry x:Name="weight" Text="{Binding Weight, Mode=TwoWay}"
                       ClearButtonVisibility="WhileEditing" Keyboard="Numeric"/>
            </VerticalStackLayout>
            <VerticalStackLayout Margin="0,10">
                <Label Text="Height" />
                <Entry x:Name="height" Text="{Binding Height, Mode=TwoWay}" 
                       ClearButtonVisibility="WhileEditing" Keyboard="Numeric"/>
            </VerticalStackLayout>
            <VerticalStackLayout Margin="0,10">
                <Label Text="Image link" />
                <Entry x:Name="webImageLink" Text="{Binding WebImageLink, Mode=TwoWay}"
                       ClearButtonVisibility="WhileEditing"/>
            </VerticalStackLayout>
            <VerticalStackLayout Margin="0,10">
                <Label Text="Description" />
                <Editor x:Name="description" Text="{Binding Description, Mode=TwoWay}"
                        AutoSize="TextChanges"/>
            </VerticalStackLayout>
        </VerticalStackLayout>
    </ScrollView>
</ContentPage>

View code behind:

[QueryProperty(nameof(Player), "player")]
public partial class AddOrUpdatePlayer : ContentPage
{
    private PlayerModel player;
    public PlayerModel Player
    {
        get => player;
        set
        {
            player = value;
            OnPropertyChanged();
        }
    }


    private readonly IPositionClient positionClient;
    private readonly IPlayerClient playerClient;

    private delegate Task Action();
    private Action asyncAction;

    public AddOrUpdatePlayer(IPositionClient positionClient, IPlayerClient playerClient)
    {
        InitializeComponent();
        SetUpControls();
        SetTitle();
        SetActionPointer();

        this.positionClient = positionClient;
        this.playerClient = playerClient;
    }

    protected async override void OnAppearing()
    {
        BindingContext = player;
        await SetUpPositionPicker();
    }

    private void SetUpControls()
    {
        birthday.MinimumDate = new DateTime(1900, 1, 1);
        birthday.MaximumDate = DateTime.Now.Date;
    }

    private async Task SetUpPositionPicker()
    {
        position.ItemsSource = await this.positionClient.GetAllAsync();
    }

    private void SetTitle()
    {
        Title = this.player is null ?
                "Add new player" :
                 $"Update {player.Name}";
    }

    private void SetActionPointer()
    {
        asyncAction = this.player is null ?
                      AddNewPlayer :
                      UpdatePlayer;
    }

    private async Task AddNewPlayer()
    {
        var player = BindingContext as PlayerModel;

        await playerClient.CreateAsync(player);

        await Shell.Current.GoToAsync(PageRoutes.HomePage, true);
    }

    private async Task UpdatePlayer()
    { }

    private async void OnSaveClick(object sender, EventArgs e)
    {
        await asyncAction();
    }
}

Global style:

<?xml version="1.0" encoding="UTF-8" ?>
<?xaml-comp compile="true" ?>
<ResourceDictionary
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:mct="http://schemas.microsoft.com/dotnet/2022/maui/toolkit">

    <Entry.Style>
        <OnPlatform x:TypeArguments="Style">
            <On Platform="iOS, Android" Value="{StaticResource EntryStyle}" />
            <On Platform="WinUI" Value="{StaticResource WinUIEntryStyle}" />
        </OnPlatform>
    </Entry.Style>
    <Entry.Behaviors>
        <mct:EventToCommandBehavior
        EventName="TextChanged"
        Command="{Binding Validate}" />
    </Entry.Behaviors>
    <Entry.Triggers>
        <DataTrigger
        TargetType="Entry"
        Binding="{Binding UserName.IsValid}"
        Value="False">
            <Setter Property="BackgroundColor" Value="{StaticResource ErrorColor}" />
        </DataTrigger>
    </Entry.Triggers>

</ResourceDictionary>

I need to change the Entry.Triggers DataTrigger Binding property to adapt to my solution if it's possible. My another problem is that when the button is clicked to save the BindingContext is null, no data is banded to the model in the AddNewPlayer() function.

Wasyster
  • 2,279
  • 4
  • 26
  • 58
  • `Binding="{Binding UserName.IsValid}` in your Triggers code is to monitor whether the property change of `UserName` satisfies the trigger condition. You said: "I need to change the Entry.Triggers DataTrigger Binding property to adapt to my solution if it's possible.". What's your solution? You add a breakpoint in `SetActionPointer`, when the button is clicked to save the BindingContext is null, and see if that method fires. – Zack Dec 28 '22 at 05:39
  • If you saw my code, than you can see that I do no have a UserName field. I do not like the provided implementation on the Microsoft site, I want to do something similar as I did in my WPF application. In the official documnettation https://learn.microsoft.com/en-us/dotnet/architecture/maui/validation they are using IValidationRule for every property in the model, then adding every validation rule by hand in the validation collection. I do not wan't to do that, I want to use data annotations. SetActionPointer is working as it should be, the function is called, but te BindingContext = null. – Wasyster Dec 28 '22 at 08:00
  • What you bind is `PlayerModel`, `BindingContext = null`. You can check whether the `get` or `set` method in your `PlayerModel` is executed, you need to use `ObservableObject` or `INotifyPropertyChanged`. – Zack Dec 29 '22 at 06:20
  • It got changed. When I press the save button, the changes are in BindingContext. – Wasyster Dec 29 '22 at 07:09
  • Your problem has been solved, right? – Zack Dec 29 '22 at 09:35
  • No, I think without error templates on Entry, we can't do this. – Wasyster Dec 29 '22 at 10:15
  • You can try to create a control template, more can refer to:[Control templates](https://learn.microsoft.com/en-us/dotnet/maui/fundamentals/controltemplate?view=net-maui-7.0) – Zack Jan 03 '23 at 02:23

0 Answers0