I have a state class named PlayerState that contains many fields I want to bind via XAML, so I want a solution that scales well.
I have this code in a ViewModel that inherits from the BindableBase class provided by Microsoft so that binding gets triggered when SetProperty is called. I have verified that SetProperty is called with data that is correct.
private PlayerState _playerState;
public PlayerState PlayerState
{
get { return _playerState; }
set { SetProperty(ref _playerState, value); }
}
internal void OnStateChange(PlayerState playerState)
{
PlayerState = playerState;
}
I have this binding that contains a context to the ViewModel described above, and have no clue how I am supposed to debug XAML. The error I get is that 'Active' property not found on 'App.Models.PlayerState'
. All the fields in PlayerState are public and readonly.
<Image
x:Name="ActiveImage"
Source="{Binding PlayerState.Active.ImageFileNames[ImageSize.SMALL], Mode=OneWay, FallbackValue=/Assets/BlankCard2.png}"
Margin="5"
Grid.Row="0"
Grid.Column="5" />
I set the context in the code behind file.
public PlayerPage()
{
InitializeComponent();
DataContext = ViewModel;
}
But when the PlayerState's Active field is changed and PlayerState updated, the binding does not update the image. Why?
The path Active.ImageFileNames[ImageSize.SMALL]
is correct, because it works when I bind them to a ListView.
I would use bind
, but it does not support indexing, so what could be wrong here?
I'm assuming that ImageSize
is an enum
, something like this:
public enum ImageSize
{
SMALL,
MEDIUM,
LARGE,
}
But IIRC, bindings won't work with Dictionary
with enum
as its key, in this case, Dictionary<ImageSize, string>
.
So, one workaround should be using Dictionary<string, string>
.
Another workaround might be creating a value converter:
public class TestConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is IDictionary<ImageSize, string> dictionary &&
parameter is string parameterString &&
Enum.TryParse(parameterString, out ImageSize imageSize) is true)
{
if (dictionary.TryGetValue(imageSize, out var fileName))
{
return fileName;
}
}
throw new ArgumentException($"Failed to convert to file name. [value: {value} / parameter: {parameter}]");
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
and use it like this:
<Image Source="{Binding PlayerState.Active.ImageFileNames, Mode=OneWay, Converter={StaticResource TestConverter}, ConverterParameter='SMALL'}" />
UPDATE
This is the rest of my test code for this answer:
public class Active
{
public Dictionary<ImageSize, string> ImageFileNames { get; } = new()
{
{ ImageSize.SMALL, "Assets/Small.png" },
{ ImageSize.MEDIUM, "Assets/Medium.png" },
{ ImageSize.LARGE, "Assets/Large.png" },
};
}
public partial class PlayerState : ObservableObject
{
[ObservableProperty]
private Active? active;
}
public partial class MainPageViewModel : ObservableObject
{
[ObservableProperty]
private PlayerState playerState = new();
[RelayCommand]
private void Test()
{
PlayerState.Active = new Active();
}
}
NOTE
I'm using the CommunityToolkit.Mvvm NuGet package for the MVVM pattern and I recommend you give it a try. It's just amazing.