79739719

Date: 2025-08-19 09:45:20
Score: 0.5
Natty:
Report link

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>
Reasons:
  • Long answer (-1):
  • Has code block (-0.5):
  • User mentioned (1): @Julian
  • User mentioned (0): @Julian
  • Self-answer (0.5):
  • Low reputation (0.5):
Posted by: aardvark2012