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
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.