I have a problem with the Picker control in .NET MAUI. On the update page, the picker is not showing the value of update model. Here is how is picker defined in the xaml:
<?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 x:Name="ViewModel"/>
</ContentPage.BindingContext>
<ContentPage.ToolbarItems>
<ToolbarItem IconImageSource="save.svg" Clicked="OnSaveClick" Command="{Binding ValidateCommand}">
</ToolbarItem>
</ContentPage.ToolbarItems>
<ScrollView Margin="10">
<VerticalStackLayout>
<VerticalStackLayout>
<Label Text="Name" />
<Entry x:Name="name" Text="{Binding Name}"
ClearButtonVisibility="WhileEditing">
<Entry.Behaviors>
<toolkit:EventToCommandBehavior
EventName="TextChanged"
Command="{Binding [Name].HasError}" />
</Entry.Behaviors>
</Entry>
<Label x:Name="lblValidationErrorName" Text="{Binding [Name].Error}" TextColor="Red" />
</VerticalStackLayout>
<VerticalStackLayout Margin="0,10">
<Label Text="Position" />
<Picker x:Name="position" Title="Select..."
ItemDisplayBinding="{Binding Name}"
SelectedItem="{Binding Position}">
</Picker>
<Label x:Name="lblValidationErrorPosition" TextColor="red" Text="{Binding [Position].Error}"/>
</VerticalStackLayout>
<VerticalStackLayout Margin="0,10">
<Label Text="Club" />
<Entry x:Name="club" Text="{Binding Club}"
ClearButtonVisibility="WhileEditing">
<Entry.Behaviors>
<toolkit:EventToCommandBehavior
EventName="TextChanged"
Command="{Binding [Club].HasError}" />
</Entry.Behaviors>
</Entry>
<Label x:Name="lblValidationErrorClub" TextColor="red" Text="{Binding [Club].Error}"/>
</VerticalStackLayout>
<VerticalStackLayout Margin="0,10">
<Label Text="Birthday" />
<DatePicker x:Name="birthday" Date="{Binding Birthday}"/>
</VerticalStackLayout>
<VerticalStackLayout Margin="0,10">
<Label Text="Birth place" />
<Entry x:Name="birthplace" Text="{Binding BirthPlace}"
ClearButtonVisibility="WhileEditing">
<Entry.Behaviors>
<toolkit:EventToCommandBehavior
EventName="TextChanged"
Command="{Binding [BirthPlace].HasError}" />
</Entry.Behaviors>
</Entry>
<Label x:Name="lblValidationErrorBirthPlace" TextColor="red" Text="{Binding [BirthPlace].Error}"/>
</VerticalStackLayout>
<VerticalStackLayout Margin="0,10">
<Label Text="Weight" />
<Entry x:Name="weight" Text="{Binding Weight}"
ClearButtonVisibility="WhileEditing" Keyboard="Numeric">
<Entry.Behaviors>
<toolkit:EventToCommandBehavior
EventName="TextChanged"
Command="{Binding [Weight].HasError}" />
</Entry.Behaviors>
</Entry>
<Label x:Name="lblValidationErrorWeight" TextColor="red" Text="{Binding [Weight].Error}" />
</VerticalStackLayout>
<VerticalStackLayout Margin="0,10">
<Label Text="Height" />
<Entry x:Name="height" Text="{Binding Height}"
ClearButtonVisibility="WhileEditing" Keyboard="Numeric">
<Entry.Behaviors>
<toolkit:EventToCommandBehavior
EventName="TextChanged"
Command="{Binding [Height].HasError}" />
</Entry.Behaviors>
</Entry>
<Label x:Name="lblValidationErrorHeight" TextColor="red" Text="{Binding [Height].Error}" />
</VerticalStackLayout>
<VerticalStackLayout Margin="0,10">
<Label Text="Image link" />
<Entry x:Name="webImageLink" Text="{Binding WebImageLink}"
ClearButtonVisibility="WhileEditing">
<Entry.Behaviors>
<toolkit:EventToCommandBehavior
EventName="TextChanged"
Command="{Binding [WebImageLink].HasError}" />
</Entry.Behaviors>
</Entry>
<Label x:Name="lblValidationErrorWebImageLink" TextColor="red" Text="{Binding [WebImageLink].Error}"/>
</VerticalStackLayout>
<VerticalStackLayout Margin="0,10">
<Label Text="Description" />
<Editor x:Name="description" Text="{Binding Description}"
AutoSize="TextChanges">
<Editor.Behaviors>
<toolkit:EventToCommandBehavior
EventName="TextChanged"
Command="{Binding [Description].HasError}" />
</Editor.Behaviors>
</Editor>
<Label x:Name="lblValidationErrorDescription" TextColor="red" Text="{Binding [Description].Error}"/>
</VerticalStackLayout>
</VerticalStackLayout>
</ScrollView>
</ContentPage>
The code behind:
[QueryProperty(nameof(Player), "player")]
public partial class AddOrUpdatePlayer : ContentPage
{
private PlayerModel player;
public PlayerModel Player
{
get => player;
set
{
player = value;
OnPropertyChanged("player");
}
}
private readonly IMemoryCache memoryCache;
private readonly IPlayerClient playerClient;
private delegate Task Action();
private Action asyncAction;
public AddOrUpdatePlayer(IMemoryCache memoryCache, IPlayerClient playerClient)
{
this.memoryCache = memoryCache;
this.playerClient = playerClient;
InitializeComponent();
SetUpPositionPicker();
}
protected override void OnAppearing()
{
player ??= new PlayerModel();
player.ValidationCompleted += OnValidationHandler;
BindingContext = player;
SetUpControls();
SetTitle();
SetActionPointer();
}
private void SetUpControls()
{
birthday.MinimumDate = new DateTime(1900, 1, 1);
birthday.MaximumDate = DateTime.Now.Date;
memoryCache.TryGetValue(CacheKeys.Positions, out List<PositionModel> positions);
var selectedPosition = positions.FirstOrDefault(x => x.Id == player?.Position?.Id);
var index = positions.IndexOf(selectedPosition);
position.SelectedIndex = index;
}
private void SetUpPositionPicker()
{
memoryCache.TryGetValue(CacheKeys.Positions, out List<PositionModel> positions);
position.ItemsSource = positions;
}
private void SetTitle()
{
Title = this.player?.Id == 0 ?
"Add new player" :
$"Update {player?.Name}";
}
private void SetActionPointer()
{
asyncAction = this.player?.Id == 0 ?
AddNewPlayer :
UpdatePlayer;
}
private async Task AddNewPlayer()
{
var result = await playerClient.CreateAsync(player);
if (!result)
return;
}
private async Task UpdatePlayer()
{
var result = await playerClient.UpdateAsync(player);
if (!result)
return;
}
private async void OnSaveClick(object sender, EventArgs e)
{
if (player?.HasErrors ?? true)
return;
await asyncAction();
}
private void OnValidationHandler(Dictionary<string, string?> validationMessages)
{
if (validationMessages is null)
return;
lblValidationErrorName.Text = validationMessages.GetValueOrDefault("name");
lblValidationErrorPosition.Text = validationMessages.GetValueOrDefault("positionid");
lblValidationErrorClub.Text = validationMessages.GetValueOrDefault("club");
lblValidationErrorWebImageLink.Text = validationMessages.GetValueOrDefault("webimagelink");
lblValidationErrorBirthPlace.Text = validationMessages.GetValueOrDefault("birthplace");
lblValidationErrorWeight.Text = validationMessages.GetValueOrDefault("weight");
lblValidationErrorHeight.Text = validationMessages.GetValueOrDefault("height");
lblValidationErrorDescription.Text = validationMessages.GetValueOrDefault("description");
}
}
public partial class PlayerModel : BaseViewModel
{
private int id;
private string name;
private string webImageLink;
private string club;
private string birthday;
private string birthPlace;
private int? weight;
private double? height;
private string description;
private PositionModel position;
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
{
SetProperty(ref this.name, value, true);
ClearErrors();
SetProperty(ref this.name, value);
ValidateAllProperties();
OnPropertyChanged("ErrorDictionary[Name]");
}
}
[Required]
[StringLength(4096)]
public string WebImageLink
{
get => this.webImageLink;
set
{
SetProperty(ref this.webImageLink, value, true);
ClearErrors();
ValidateAllProperties();
OnPropertyChanged("ErrorDictionary[WebImageLink]");
}
}
[Required]
[StringLength(255)]
[MinLength(2)]
public string Club
{
get => this.club;
set
{
SetProperty(ref this.club, value, true);
ClearErrors();
ValidateAllProperties();
OnPropertyChanged("ErrorDictionary[Club]");
}
}
[Required]
[StringLength(32)]
public string Birthday
{
get => this.birthday;
set
{
SetProperty(ref this.birthday, value, true);
ClearErrors();
ValidateAllProperties();
OnPropertyChanged("ErrorDictionary[Birthday]");
}
}
[Required]
[StringLength(255)]
public string BirthPlace
{
get => this.birthPlace;
set
{
SetProperty(ref this.birthPlace, value, true);
ClearErrors();
ValidateAllProperties();
OnPropertyChanged("ErrorDictionary[BirthPlace]");
}
}
[Required]
[Range(0, 100)]
public int? Weight
{
get => this.weight;
set
{
SetProperty(ref this.weight, value, true);
ClearErrors();
ValidateAllProperties();
OnPropertyChanged("ErrorDictionary[Weight]");
}
}
[Required]
[Range(0, 2.5)]
public double? Height
{
get => this.height;
set
{
SetProperty(ref this.height, value, true);
ClearErrors();
ValidateAllProperties();
OnPropertyChanged("ErrorDictionary[Height]");
}
}
[Required]
public string Description
{
get => this.description;
set
{
SetProperty(ref this.description, value, true);
ClearErrors();
ValidateAllProperties();
OnPropertyChanged("ErrorDictionary[Description]");
}
}
[Required]
public PositionModel Position
{
get => this.position;
set
{
SetProperty(ref this.position, value, true);
ClearErrors();
ValidateAllProperties();
OnPropertyChanged("ErrorDictionary[Name]");
}
}
public PlayerModel() : base()
{}
public PlayerModel(int id, string name, string webImageLink, string club, string birthday, string birthPlace, int weight, double height, string description, string positionName, int positionId) : base()
{
Id = id;
Name = name;
WebImageLink = webImageLink;
Club = club;
Birthday = birthday;
BirthPlace = birthPlace;
Weight = weight;
Height = height;
Description = description;
Position = new PositionModel(positionId, positionName);
}
public PlayerModel(int id, string name, string webImageLink, string club, string birthday, string birthPlace, int weight, double height, string description, PositionModel position) : base()
{
Id = id;
Name = name;
WebImageLink = webImageLink;
Club = club;
Birthday = birthday;
BirthPlace = birthPlace;
Weight = weight;
Height = height;
Description = description;
Position = position;
}
public PlayerModel(PlayerEntity player)
{
Id = player.Id;
Name = player.Name;
WebImageLink = player.WebImageLink;
Club = player.Club;
Birthday = player.Birthday;
BirthPlace = player.BirthPlace;
Weight = player.Weight;
Height = player.Height;
Description = player.Description;
Position = new PositionModel(player.Position);
}
public PlayerEntity ToEntity()
{
return new PlayerEntity
{
Id = Id,
Name = Name,
WebImageLink = WebImageLink,
Club = Club,
Birthday = Birthday,
BirthPlace = BirthPlace,
Weight = Weight.Value,
Height = Height.Value,
Description = Description,
PositionId = Position.Id
};
}
public void ToEntity(PlayerEntity player)
{
player.Id = Id;
player.Name = Name;
player.WebImageLink = WebImageLink;
player.Club = Club;
player.Birthday = Birthday;
player.BirthPlace = BirthPlace;
player.Weight = Weight.Value;
player.Height = Height.Value;
player.Description = Description;
player.PositionId = Position.Id;
}
}
public delegate void NotifyWithValidationMessages(Dictionary<string, string?> validationDictionary);
public class BaseViewModel : ObservableValidator
{
public event NotifyWithValidationMessages? ValidationCompleted;
public virtual ICommand ValidateCommand => new RelayCommand(() =>
{
ClearErrors();
ValidateAllProperties();
var validationMessages = this.GetErrors()
.ToDictionary(k => k.MemberNames.First().ToLower(), v => v.ErrorMessage);
ValidationCompleted?.Invoke(validationMessages);
});
[IndexerName("ErrorDictionary")]
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 BaseViewModel() : base()
{}
}
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;
}
}
public class PositionModel
{
[Required]
[Range(1, 7)]
public int Id { get; set; }
[Required]
[StringLength(255)]
public string Name { get; set; }
public PositionModel()
{
}
public PositionModel(int id, string name)
{
Id = id;
Name = name;
}
public PositionModel(PositionEntity entity)
{
Id = entity.Id;
Name = entity.Name;
}
public PositionEntity ToEntity()
{
return new PositionEntity
{
Id = Id,
Name = Name
};
}
public void ToEntity(PositionEntity entity)
{
entity.Id = Id;
entity.Name = Name;
}
What is very interesting, if I edit the first object everything works as it, but if I navigate on the second object and try to edit it, the position is not set. And so on, the third object will have set the position, the 4th won't, and so on. Any idea?