.netdata-bindingmauibinding-context

BindingContext is failing in MAUI


I don't fully understand how binding context works in maui. I have MainPage that uses this datagrid:

<dataGrid:DataGridView Grid.Row="0" Grid.RowSpan="2"
                       ItemsSource="{Binding Expenses}"
                       SelectionMode="None"
                       IndicatorColor="Transparent"
                       IsRefreshing="{Binding IsRefreshing, Mode=TwoWay}"
                       IsLoadMoreEnabled="{Binding IsLoadMore}"
                       LoadMoreCommand="{Binding LoadMoreCommand}"
                       BackgroundColor="{StaticResource White}"
                       IsColumnHeaderVisible="False">
    <dataGrid:DataGridView.Columns>
        <dataGrid:TemplateColumn AllowSort="False">
            <dataGrid:TemplateColumn.DisplayTemplate>
                <DataTemplate x:DataType="models:CategoryModel">
                    <templates:ExpenseCategoryListItemTemplate />
                </DataTemplate>
            </dataGrid:TemplateColumn.DisplayTemplate>
        </dataGrid:TemplateColumn>
    </dataGrid:DataGridView.Columns>
</dataGrid:DataGridView>

And when i write x:DataType i think it should change BindingContext to that model. Then inside template:

<Grid Padding="16,16,16,0" ColumnSpacing="16" BackgroundColor="{StaticResource White}">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition Width="85*" />
    </Grid.ColumnDefinitions>
    <local:RoundIconComponent Grid.Column="0"
                              HeightRequest="32"
                              WidthRequest="32"
                              IconBackgroundColor="{Binding Item.IconColor}"
                              IconName="{Binding Item.IconName}"
                              DefaultIconName="ic_arrow_up_red_small"
                              IconHeightRequest="20"/>

Item.IconColor or Item.IconName is not null and values are correct. This is my RoundIconComponent.xaml:

<Border 
        BackgroundColor="{Binding IconBackgroundColor}"
        StrokeShape="RoundRectangle 50"
        StrokeThickness="0">
    <Image Source="{Binding IconName}"
               Aspect="AspectFit"
               HeightRequest="{Binding IconHeightRequest}"
               HorizontalOptions="Center"
               VerticalOptions="Center" />
</Border>

And this is xaml.cs:

public partial class RoundIconComponent : ContentView {
    public static readonly BindableProperty IconBackgroundColorProperty =
        BindableProperty.Create(nameof(IconBackgroundColor), typeof(string), typeof(RoundIconComponent), string.Empty);
    public static readonly BindableProperty DefaultIconBackgroundColorProperty =
        BindableProperty.Create(nameof(DefaultIconBackgroundColor), typeof(string), typeof(RoundIconComponent), "SteelBlue");
    public static readonly BindableProperty IconNameProperty =
        BindableProperty.Create(nameof(IconName), typeof(string), typeof(RoundIconComponent), string.Empty);
    public static readonly BindableProperty IconHeightRequestProperty =
        BindableProperty.Create(nameof(IconHeightRequest), typeof(int), typeof(RoundIconComponent), 20);
    public static readonly BindableProperty DefaultIconNameProperty =
        BindableProperty.Create(nameof(DefaultIconName), typeof(string), typeof(RoundIconComponent), "shopping_bag");

    public string IconBackgroundColor { get => (string)GetValue(IconBackgroundColorProperty); set => SetValue(IconBackgroundColorProperty, value); }
    public string DefaultIconBackgroundColor { get => (string)GetValue(DefaultIconBackgroundColorProperty); set => SetValue(DefaultIconBackgroundColorProperty, value); }
    public string IconName { get => (string)GetValue(IconNameProperty); set => SetValue(IconNameProperty, value); }
    public string DefaultIconName { get => (string)GetValue(DefaultIconNameProperty); set => SetValue(DefaultIconNameProperty, value); }
    public int IconHeightRequest { get => (int)GetValue(IconHeightRequestProperty); set => SetValue(IconHeightRequestProperty, value); }

    public RoundIconComponent() {
        this.BindingContext = this;
        InitializeComponent();
    }
}

I will be grateful if someone can tell me why its not working here and what i can do

Edit: i have only set BindingContext in MainPage:

public MainPage(MainViewModel mainViewModel, IUserContext userContext, ISettingsStore settingsStore) {
    BindingContext = _mainViewModel = mainViewModel;

I dont have bindings set in Template nor in component itself. If i remove my RoundIconComponent and write everything manually, then it resolves binding perfectly.


Solution

  • The reason this does not work is simple actually, you are assigning the BindingContext of the custom control in your constructor, which gets overridden as soon as you use this inside a Page, as the page's context is automatically assigned to all controls that are part of the page, the correct way of creating custom control in MAUI that use binding is to use the Source of your binding as the current control so in your case your custom control would look like this :

    public partial class RoundIconComponent : ContentView 
    {
        public static readonly BindableProperty IconBackgroundColorProperty =
            BindableProperty.Create(nameof(IconBackgroundColor), typeof(string), typeof(RoundIconComponent), string.Empty);
        public static readonly BindableProperty DefaultIconBackgroundColorProperty =
            BindableProperty.Create(nameof(DefaultIconBackgroundColor), typeof(string), typeof(RoundIconComponent), "SteelBlue");
        public static readonly BindableProperty IconNameProperty =
            BindableProperty.Create(nameof(IconName), typeof(string), typeof(RoundIconComponent), string.Empty);
        public static readonly BindableProperty IconHeightRequestProperty =
            BindableProperty.Create(nameof(IconHeightRequest), typeof(int), typeof(RoundIconComponent), 20);
        public static readonly BindableProperty DefaultIconNameProperty =
            BindableProperty.Create(nameof(DefaultIconName), typeof(string), typeof(RoundIconComponent), "shopping_bag");
    
        public string IconBackgroundColor { get => (string)GetValue(IconBackgroundColorProperty); set => SetValue(IconBackgroundColorProperty, value); }
        public string DefaultIconBackgroundColor { get => (string)GetValue(DefaultIconBackgroundColorProperty); set => SetValue(DefaultIconBackgroundColorProperty, value); }
        public string IconName { get => (string)GetValue(IconNameProperty); set => SetValue(IconNameProperty, value); }
        public string DefaultIconName { get => (string)GetValue(DefaultIconNameProperty); set => SetValue(DefaultIconNameProperty, value); }
        public int IconHeightRequest { get => (int)GetValue(IconHeightRequestProperty); set => SetValue(IconHeightRequestProperty, value); }
    
        public RoundIconComponent() 
        {
            InitializeComponent();
        }
    }
    

    And its XAML would access its code behind like this:

    <ContentView
    .
    .
    .
    x:Name="this">
       <Border 
            BackgroundColor="{Binding IconBackgroundColor,Source={x:Reference this}}"
            StrokeShape="RoundRectangle 50"
            StrokeThickness="0">
           <Image Source="{Binding IconName,Source={x:Reference this}}"
                   Aspect="AspectFit"
                   HeightRequest="{Binding IconHeightRequest,Source={x:Reference this}}"
                   HorizontalOptions="Center"
                   VerticalOptions="Center" />
       </Border>
    </ContentView
    

    Once you do this, you don't need to assign the BindingContext to itself and you can use this control pretty much anywhere