According to @Julian from this answer:
It seems that Drawables cannot be used with
BindableProperty, at least it doesn't have any effect, the value of the property doesn't get updated.
The workaround suggested by @Julian is to put the BindableProperty into a class derived from GraphicsView instead of the one implementing IDrawable.
I have adapted my code to the answer he gave there, and I'm posting it in its entirety in case someone is looking for a stripped down example of how to do this.
First, here is the the code for GraphView, incorporating the BindableProperty:
namespace TestNET8.Drawables;
public partial class GraphView : GraphicsView
{
public float[] Data
{
get => (float[])GetValue(DataProperty);
set => SetValue(DataProperty, value);
}
public static readonly BindableProperty DataProperty = BindableProperty.Create(nameof(Data), typeof(float[]), typeof(GraphView), propertyChanged: DataPropertyChanged);
private static void DataPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
if (bindable is not GraphView { Drawable: GraphDrawable drawable } view)
{
return;
}
drawable.Data = (float[])newValue;
view.Invalidate();
}
}
Then the Drawable just has a public property for the data and the Draw function:
namespace TestNET8.Drawables;
public partial class GraphDrawable : IDrawable
{
public float[] Data { get; set; } = new float[100];
public void Draw(ICanvas canvas, RectF dirtyRect)
{
// Creates a time series plot from the data values. Replace with whatever makes sense
if (Data != null && Data.Length > 0)
{
for (int i = 0; i < Data.Length - 1; i++)
{
canvas.DrawLine(5 * i, 100 * Data[i], 5 * (i + 1), 100 * Data[i + 1]);
}
}
}
}
GraphView calls Invalidate(), so there's no need to call it from the code-behind:
using TestNET8.ViewModels;
namespace TestNET8
{
public partial class MainPage : ContentPage
{
public MainPage(MainViewModel vm)
{
InitializeComponent();
BindingContext = vm;
}
}
}
In the view model, when DataHolder is updated following a button click, the [ObservableProperty] (from the CommunityToolkit.Mvvm) automatically triggers the OnPropertyChanged event:
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
namespace TestNET8.ViewModels;
public partial class MainViewModel : ObservableObject
{
[ObservableProperty]
float[] dataHolder = new float[100];
[RelayCommand]
public void Refresh()
{
// Generates an array of random values when the button is clicked
var rand = new Random();
float[] temp = new float[100];
for (int i = 0; i < 100; i++)
{
temp[i] = rand.NextSingle();
}
DataHolder = temp;
}
}
Finally, GraphView.Data is bound to ViewModel.DataHolder (and the button click is bound to the Refresh command) 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"
xmlns:viewmodel="clr-namespace:TestNET8.ViewModels"
xmlns:drawables="clr-namespace:TestNET8.Drawables"
x:DataType="viewmodel:MainViewModel"
x:Class="TestNET8.MainPage">
<VerticalStackLayout>
<drawables:GraphView
x:Name="GraphView"
HeightRequest="200"
WidthRequest="500"
Data="{Binding DataHolder}">
<drawables:GraphView.Drawable>
<drawables:GraphDrawable/>
</drawables:GraphView.Drawable>
</drawables:GraphView>
<Button
HeightRequest="40"
WidthRequest="150"
Text="Refresh"
Command="{Binding RefreshCommand}"/>
</VerticalStackLayout>
</ContentPage>