How does BindingExtension know its effective binding context?

495 views Asked by At

Just as Xamarin.Forms.Xaml.BindingExtension does its magic to get the effective binding context in order to return the BindingBase object that corresponds to the relevant source/path/mode, I would like to create an extension that does its voodoo based on the effective binding context as well. Through IProvideValueTarget, I managed to get the BindingContext, however, not the EFFECTIVE binding context, thus, when used in a DataTemplate, it always brings me the effective binding context of the parent rather than the DataTemplate. I am trying to find the effective binding context inside the data template.

In order for this to be clearer, I re-created the problem in a simple PoC.

We have the following model:

namespace App1.Models
{
    public class MyBeautifulModel
    {
        public string SomeData { get; set; }
        public override string ToString()
        {
            return SomeData;
        }
    }
}

The following view-model:

using App1.Models;

using System.Collections.Generic;

namespace App1.ViewModels
{
    public class MainPageViewModel : ViewModelBase
    {
        public MainPageViewModel()
        {
            MyBeautifulModels = new[]
            {
                new MyBeautifulModel() { SomeData = "DataTemplate Data0" },
                new MyBeautifulModel() { SomeData = "DataTemplate Data1" },
                new MyBeautifulModel() { SomeData = "DataTemplate Data2" },
            };

            MyProperty = "Outer Template Data";
        }

        public IEnumerable<MyBeautifulModel> MyBeautifulModels { get; }

        public string MyProperty { get; }

        public override string ToString()
        {
            return MyProperty;
        }
    }
}

The following markup extension:

using System;
using System.Linq;
using System.Reflection;
using System.Text;

using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using Xamarin.Forms.Xaml.Internals;

namespace App1.MarkupExtensions
{
    public class PrintContextExtension : IMarkupExtension
    {
        public object ProvideValue(IServiceProvider serviceProvider)
        {
            if (serviceProvider.GetService<IProvideValueTarget>() is SimpleValueTargetProvider provider)
            {
                object[] objectAndParents = (object[])typeof(SimpleValueTargetProvider).GetField("objectAndParents", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(provider);

                StringBuilder sb = new StringBuilder();
                sb.AppendLine("Object and all its parents:");
                foreach (object obj in objectAndParents)
                    sb.AppendLine(obj.ToString());

                sb.AppendLine("First object with binding context:");
                BindableObject bindable = (BindableObject)objectAndParents.First(b => b is BindableObject bindableObject && bindableObject.BindingContext != null);
                sb.AppendLine(bindable.ToString());

                sb.AppendLine("The binding context of that object:");
                sb.AppendLine(bindable.BindingContext.ToString());
                string result = sb.ToString();
                Console.WriteLine(result);
                return bindable.BindingContext.ToString();
            }
            throw new Exception();
        }
    }
}

And the following view:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:d="http://xamarin.com/schemas/2014/forms/design"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:vm="clr-namespace:App1.ViewModels"
             xmlns:ext="clr-namespace:App1.MarkupExtensions"
             mc:Ignorable="d"
             x:Class="App1.Views.MainPage"
             Padding="20">
    <ContentPage.BindingContext>
        <vm:MainPageViewModel/>
    </ContentPage.BindingContext>
    <ContentPage.Resources>
        <Style TargetType="Label">
            <Setter Property="FontSize" Value="Title"/>
            <Setter Property="BackgroundColor" Value="LightGreen"/>
        </Style>
    </ContentPage.Resources>
    <ContentPage.Content>
        <StackLayout>
            <Label>
                <Label.FormattedText>
                    <FormattedString>
                        <Span Text="Built-in Binding: "/>
                        <Span Text="{Binding MyProperty}"/>
                    </FormattedString>
                </Label.FormattedText>
            </Label>
            <Label>
                <Label.FormattedText>
                    <FormattedString>
                        <Span Text="My Binding: "/>
                        <Span Text="{ext:PrintContext MyProperty}"/>
                    </FormattedString>
                </Label.FormattedText>
            </Label>
            <Frame BorderColor="DimGray"
                   CornerRadius="15">
                <ListView ItemsSource="{Binding MyBeautifulModels}"
                      HasUnevenRows="True">
                    <ListView.ItemTemplate>
                        <DataTemplate>
                            <ViewCell>
                                <StackLayout Margin="0,0,0,20">
                                    <Label>
                                        <Label.FormattedText>
                                            <FormattedString>
                                                <Span Text="Built-in Binding: "/>
                                                <Span Text="{Binding SomeData}"/>
                                            </FormattedString>
                                        </Label.FormattedText>
                                    </Label>
                                    <Label BackgroundColor="PaleVioletRed">
                                        <Label.FormattedText>
                                            <FormattedString>
                                                <Span Text="My Binding: "/>
                                                <Span Text="{ext:PrintContext SomeData}"/>
                                            </FormattedString>
                                        </Label.FormattedText>
                                    </Label>
                                </StackLayout>
                            </ViewCell>
                        </DataTemplate>
                    </ListView.ItemTemplate>
                </ListView>
            </Frame>
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

Running this results in the following console output:

Object and all its parents:
Xamarin.Forms.Span
My Binding: 
Xamarin.Forms.Label
Xamarin.Forms.StackLayout
Xamarin.Forms.ViewCell
Xamarin.Forms.DataTemplate
Xamarin.Forms.ListView
Xamarin.Forms.Frame
Xamarin.Forms.StackLayout
App1.Views.MainPage
First object with binding context:
Xamarin.Forms.ListView
The binding context of that object:
Outer Template Data

And finally, this is how the view looks like in the runtime: runtime screenshot

Sorry if the question looks too long but I hope my reproduce steps would make it easier to simplify and illustrate the problem.

p.s looking at BindingExtension's implementation on GitHub, it looks like IServiceProvider is not even used. That seems to be weird to me - as how does it know its context like that!

0

There are 0 answers