Search code examples
c#.netxamlbindingmaui

Binding Property Not Found - MAUI .NET 8



I'm working on a .NET MAUI project and I'm running into the following error:
[0:] Microsoft.Maui.Controls.Xaml.Diagnostics.BindingDiagnostics: Warning: 'IsAudioEnabled' property not found on 'TTIG02_Controller.Components.ToggleSettingsItem', target property: 'TTIG02_Controller.Components.ToggleSettingsItem.IsToggled'
[0:] Microsoft.Maui.Controls.Xaml.Diagnostics.BindingDiagnostics: Warning: 'IsNotificationEnabled' property not found on 'TTIG02_Controller.Components.ToggleSettingsItem', target property: 'TTIG02_Controller.Components.ToggleSettingsItem.IsToggled'

This are the relevant parts of the code:

SettingsPage.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="TTIG02_Controller.Views.SettingsPage"
             xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
             xmlns:vm="clr-namespace:TTIG02_Controller.ViewModels"
             xmlns:components="clr-namespace:TTIG02_Controller.Components"
             xmlns:local="clr-namespace:TTIG02_Controller.Views"
             Shell.ForegroundColor="Blue"
             x:DataType="vm:SettingsPageViewModel"
             Title="Impostazioni">

    <ScrollView>
        <VerticalStackLayout>

            <!-- SEZIONE PROFILO -->
            
            <toolkit:AvatarView
                WidthRequest="100"
                HeightRequest="100"
                Margin="0,24,0,0"
                Padding="-2"
                ImageSource="user.png"
                CornerRadius="50"
                BorderColor="Blue"
                StrokeThickness="4" />
            <Label
                Text="{Binding UserFullName}"
                FontSize="24"
                FontAttributes="Bold"
                HorizontalOptions="Center"
                Margin="0,10,0,0" />
            <Label
                Text="{Binding UserEmail}"
                FontSize="16"
                HorizontalOptions="Center"
                Margin="0,4,0,0" />
            
            <!-- SEZIONE IMPOSTAZIONI -->

            <components:ToggleSettingsItem
                Key="audio"
                ToggleChanged="ToggleSettingsItem_ToggleChanged"
                IconSource="icon_audio.svg"
                ItemText="Abilita suoni"
                IsToggled="{Binding IsAudioEnabled}" />

            <components:ToggleSettingsItem
                Key="notification"
                ToggleChanged="ToggleSettingsItem_ToggleChanged"
                IconSource="icon_bell.svg"
                ItemText="Abilita notifiche"
                IsToggled="{Binding IsNotificationEnabled}" />

        </VerticalStackLayout>
    </ScrollView>
    
</ContentPage>

SettingsPage.xaml.cs:

public partial class SettingsPage : ContentPage
{
    public SettingsPage()
    {
        InitializeComponent();
        BindingContext = Application.Current!.Handler.MauiContext!.Services.GetRequiredService<SettingsPageViewModel>();
    }

    private void ToggleSettingsItem_ToggleChanged(object sender, ToggledEventArgs e)
    {
        if (sender is not ToggleSettingsItem tsi) return;
        if(string.IsNullOrWhiteSpace(tsi.Key)) return;
        if(BindingContext is not SettingsPageViewModel vm) return;

        Log.Verbose("SettingsPage.ToggleSettingsItem_ToggleChanged: {Key} toggled to {IsToggled}", tsi.Key, e.Value);

        switch(tsi.Key)
        {
            case "audio":
                vm.IsAudioEnabled = e.Value;
                break;
            case "notification":
                vm.IsNotificationEnabled = e.Value;
                break;
            default:
                Log.Warning("SettingsPage.ToggleSettingsItem_ToggleChanged: Unknown key {Key}", tsi.Key);
                break;
        }

    }
}

ToggleSettingsItem.xaml:

<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:TTIG02_Controller.Components"
             x:DataType="local:ToggleSettingsItem"
             x:Class="TTIG02_Controller.Components.ToggleSettingsItem">

    <Grid Margin="12,8,12,8">

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="42" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="42" />
        </Grid.ColumnDefinitions>

        <Border
            Grid.Column="0"
            BackgroundColor="#F0F0F0"
            StrokeShape="RoundRectangle 6"
            StrokeThickness="0"
            Padding="4"
            HorizontalOptions="Center"
            VerticalOptions="Center">

            <Image
                Source="{Binding IconSource}"
                HeightRequest="32"
                WidthRequest="32"
                VerticalOptions="Center"
                HorizontalOptions="Center"
                Aspect="AspectFit" />

        </Border>

        <Label
            Grid.Column="1"
            Text="{Binding ItemText}"
            FontSize="20"
            TextColor="Black"
            VerticalOptions="Center"
            HorizontalOptions="Start"
            Margin="8,0,8,0" />

        <Switch
            x:Name="ToggleSwitch"
            WidthRequest="40"
            HeightRequest="40"
            Grid.Column="2"
            IsToggled="{Binding IsToggled, Mode=TwoWay}"
            HorizontalOptions="End"
            VerticalOptions="Center"
            OnColor="Blue"
            ThumbColor="Gray" />

    </Grid>
    
</ContentView>

ToggleSettingsItem.xaml.cs:

public partial class ToggleSettingsItem : ContentView
{
    public string Key
    {
        get => (string)GetValue(KeyProperty);
        set => SetValue(KeyProperty, value);
    }

    public string IconSource
    {
        get => (string)GetValue(IconSourceProperty);
        set => SetValue(IconSourceProperty, value);
    }

    public string ItemText
    {
        get => (string)GetValue(ItemTextProperty);
        set => SetValue(ItemTextProperty, value);
    }

    public bool IsToggled
    {
        get => (bool)GetValue(IsToggledProperty);
        set => SetValue(IsToggledProperty, value);
    }

    public static readonly BindableProperty IconSourceProperty =
        BindableProperty.Create(
            propertyName: nameof(IconSource),
            returnType: typeof(string),
            declaringType: typeof(ToggleSettingsItem),
            defaultValue: string.Empty,
            defaultBindingMode: BindingMode.OneWay
        );

    public static readonly BindableProperty ItemTextProperty =
        BindableProperty.Create(
            propertyName: nameof(ItemText),
            returnType: typeof(string),
            declaringType: typeof(ToggleSettingsItem),
            defaultValue: string.Empty,
            defaultBindingMode: BindingMode.OneWay
        );

    public static readonly BindableProperty IsToggledProperty =
        BindableProperty.Create(
            propertyName: nameof(IsToggled),
            returnType: typeof(bool),
            declaringType: typeof(ToggleSettingsItem),
            defaultValue: false,
            defaultBindingMode: BindingMode.TwoWay
        );

    public static readonly BindableProperty KeyProperty =
        BindableProperty.Create(
            propertyName: nameof(Key),
            returnType: typeof(string),
            declaringType: typeof(ToggleSettingsItem),
            defaultValue: string.Empty,
            defaultBindingMode: BindingMode.OneWay
        );

    public event EventHandler<ToggledEventArgs>? ToggleChanged;

    public ToggleSettingsItem()
    {
        InitializeComponent();
        ToggleSwitch.Toggled += OnToggleSwitchToggled;
    }

    private void OnToggleSwitchToggled(object? sender, ToggledEventArgs e)
    {
        Log.Verbose("ToggleSettingsItem.OnToggleSwitchToggled: {ItemText} toggled to {IsToggled}", ItemText, IsToggled);
        ToggleChanged?.Invoke(this, e);
    }
}

SettingsPageViewModel.cs:

namespace TTIG02_Controller.ViewModels
{
    public class SettingsPageViewModel : INotifyPropertyChanged
    {
        private bool _isAudioEnabled;
        public bool IsAudioEnabled
        {
            get => _isAudioEnabled;
            set
            {
                if (_isAudioEnabled != value)
                {
                    _isAudioEnabled = value;
                    OnPropertyChanged(nameof(IsAudioEnabled));
                }
            }
        }

        private bool _isNotificationEnabled;
        public bool IsNotificationEnabled
        {
            get => _isNotificationEnabled;
            set
            {
                if (_isNotificationEnabled != value)
                {
                    _isNotificationEnabled = value;
                    OnPropertyChanged(nameof(IsNotificationEnabled));
                }
            }
        }

        public event PropertyChangedEventHandler? PropertyChanged;

        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Following suggestions on similar posts I already tried:

  • Remove x:DataType
  • Set xmlns:local
  • Remove BindingContext = this; from ToggleSettingsItem
  • Doing IsToggled="{Binding Source={x:Reference SettingsPage}, Path=BindingContext.IsNotificationEnabled}"

Can someone help me finding out what's causing this problem?
Thanks!


Solution

  • First things First, Those are Warnings not Errors.

    If you set BindingContext = this in your ToggleSettingsItem.xaml.cs

    It means all the Bindings of ToggleSettingsItem are expected to come from itself, Including those you defined in SettingsPage.xaml. For example {Binding IsAudioEnabled}

    However, IsAudioEnabled is a property of your SettingsPageViewModel. Since the warning, as bindings are expecting properties on the ToggleSettingsItem

    If you want to keep BindingContext = this for ToggleSettingsItem. Then you need to explicitly reference your binding's in SettingsPage.xaml

    Step1: Define a x:Name for you SettingsPage.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="TTIG02_Controller.Views.SettingsPage"
                 x:Name="settingPage"
    

    Step 2: Explicitly reference your Binding using x:Reference

    <components:ToggleSettingsItem
                    Key="audio"
                    ToggleChanged="ToggleSettingsItem_ToggleChanged"
                    IconSource="icon_audio.svg"
                    ItemText="Abilita suoni"
                    IsToggled="{Binding Source={x:Reference settingPage},Path= BindingContext.IsAudioEnabled}" />
    

    Note: x:Reference is the x:Name given to the page