Search code examples
c#xamlbindingmaui

Property not found on ViewModel value (.NET MAUI XAML)


I'm having an issue dealing with updating my XAML. I'm using a library that reads data from UDP and in my code and parses the data from the packets so I'm trying to update my XAML accordingly, just with one property at the moment. The code compiles fine and the app is executed, but my debug output is filled with the same warning that the property I'm looking for is not found on the value on my viewmodel. The value is an instance of a struct. Any help?

PS: It's my first time dealing with MVVM so I'm still kinda confused.


MainPage.xaml.cs

using F1Telemetry;
using System.Diagnostics;

namespace TestApp;

public partial class MainPage : ContentPage
{
    TelemetryViewModel telemetryViewModel;
    public MainPage()
    {
        InitializeComponent();

        F1TelemetryClient client = new F1TelemetryClient(20777);

        telemetryViewModel = new TelemetryViewModel();
        this.BindingContext = telemetryViewModel;

        client.OnCarTelemetryDataReceive += Client_OnCarTelemetryDataReceive;
    }

    private void Client_OnCarTelemetryDataReceive(PacketCarTelemetryData packet)
    {
        int playerIndex = packet.Header.playerCarIndex;

        CarTelemetryData carTelemetryData = packet.carTelemetryData[playerIndex];

        telemetryViewModel.Data = carTelemetryData;
    }
}

MainPage.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:local="clr-namespace:TestApp"
             x:Class="TestApp.MainPage"
             xmlns:F1Telemetry="clr-namespace:F1Telemetry;assembly=F1Telemetry">

    <ContentPage.BindingContext>
        <local:TelemetryViewModel />
    </ContentPage.BindingContext>
    <ScrollView>
        <VerticalStackLayout
            Spacing="25"
            Padding="30,0"
            VerticalOptions="Center">
            <Label
                BindingContext="{Binding Data}"
                Text="{Binding engineRPM}"
                FontSize="18"
                HorizontalOptions="Center" />

        </VerticalStackLayout>
    </ScrollView>

</ContentPage>

TelemetryViewModel.cs

using F1Telemetry;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.CompilerServices;

namespace TestApp
{
    public class TelemetryViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private CarTelemetryData _data;

        public CarTelemetryData Data
        {
            get => _data;
            set
            {
                _data = value;
                OnPropertyChanged();
            }
        }

        public TelemetryViewModel()
        {
            this.Data = new CarTelemetryData();
        }

        private void OnPropertyChanged([CallerMemberName] string name = "") =>
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
    }
}

CarTelemetryData.cs (trimmed some of the attributes)

using System.Runtime.InteropServices;

namespace F1Telemetry
{
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct CarTelemetryData
    {
        public ushort speed;
        public float throttle;
        public float steer;
        public float brake;
        public byte clutch;
        public sbyte gear;
        public ushort engineRPM;
        public byte drs;
        public byte revLightsPercent;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
        public ushort[] brakesTemperature;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
        public byte[] tyresSurfaceTemperature;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
        public byte[] tyresInnerTemperature;
        public ushort engineTemperature;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
        public float[] tyresPressure;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
        public byte[] surfaceType;
    }

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct PacketCarTelemetryData
    {
        public PacketHeader Header;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)]
        public CarTelemetryData[] carTelemetryData;
        public ButtonFlags buttonStatus;
        public MFDPanel mfdPanelIndex;
        public MFDPanel mfdPanelIndexSecondaryPlayer;
        public sbyte suggestedGear;
    }
}

I checked if the OnPropertyChanged() is being called and effectively it's called every time (60 times per second, in fact) and the value in the viewmodel seems to change every time, for some reason (maybe a silly one) XAML assumes my value doesn't have the engineRPM property. I also tried using just {Binding Data.engineRPM} instead and doesn't work either.

Thanks in advance!


Solution

  • As a summary, there are several problems with your code.

    1.you have set BindingContext for your pages two times,one is in file MainPage.xaml :

      <ContentPage.BindingContext>
        <local:TelemetryViewModel />
    </ContentPage.BindingContext>
    

    One is in the construtor of MainPage.xaml.cs:

    public partial class MainPage : ContentPage
    {
        TelemetryViewModel telemetryViewModel;
        public MainPage()
        {
            InitializeComponent();
    
            F1TelemetryClient client = new F1TelemetryClient(20777);
    
    
            // set BindingContext here
            telemetryViewModel = new TelemetryViewModel();
            this.BindingContext = telemetryViewModel;
    
            client.OnCarTelemetryDataReceive += Client_OnCarTelemetryDataReceive;
        }
    
    }
    

    please remove one of above methods,use one of them.

    2.If we want to bind a proprety to the UI, we usually need to make sure it to be like this:

    public ushort engineRPM {get;set;}
    

    And if UI refresh itself while changing the value of the proprety, we also need to implement interface INotifyPropertyChanged for the model and call method OnPropertyChanged() for the properties we want to change the value.