Search code examples
c#xamarin.formsbinding-context

Cast Binding Path so it recognises ViewModel property at Design-Time


Ok this is more of an annoyance than a problem. There is no error

Page

<ContentPage
   ...
   x:Name="This"
   //hack to have typed xaml at design-time
   BindingContext="{Binding Source={x:Static viewModels:ViewModelLocator.ChooseTargetLocationVm}}"

SubView

<views:ProductStandardView
    ...
    BindingContext="{Binding Product}">
    <Grid.Triggers>
        <DataTrigger
            Binding="{Binding Path=BindingContext.IsVacate, Source={x:Reference This}}"
            TargetType="Grid"
            Value="true">
            <Setter Property="BackgroundColor" Value="{StaticResource WarningColor}" />
        </DataTrigger>
    </Grid.Triggers>

When Binding to BindingContext from the Source Reference of This, i get a XAML "warning"

Cannot resolve property 'IsVacate' in data context of type 'object'

Binding="{Binding Path=BindingContext.IsVacate, Source={x:Reference This}}"

Obviously the BindingContext is an object and untyped. However the above code compiles and works

What i want to do is cast it, firstly because i have OCD, however mainly because its easy to spot real problems on the IDE page channel bar

The following seems logical but doesn't work

Binding="{Binding Path=BindingContext.(viewModels:ChooseTargetLocationVm.IsVacate), 
                  Source={x:Reference This}}"

In the output i get

[0:] Binding: '(viewModels:ChooseTargetLocationVm' property not found on 'Inhouse.Mobile.Standard.ViewModels.ChooseTargetLocationVm', target property: 'Inhouse.Mobile.Standard.Views.ProductStandardView.Bound'

I understand the error, yet how else would i cast?


And just for stupidity, obviously the following wont compile

Binding="{Binding Path=((viewModels:ChooseTargetLocationVm)BindingContext).IsVacate, Source={x:Reference This}}"

So is there a way to cast a BindingContext to a ViewModel so any SubProperty references are typed at design time?

Update

This is relevant for inside a DataTemplate or in this case when the control has its own BindingContext which is why i need to use the Source={x:Reference This} to target the page.

Note : <ContentPage.BindingContext> doesn't work for me as i'm using prism and unity and it doesn't seem to play with well a default constructor on initial tests, though i might play around with this some more


Solution

  • You can extend ContentPage to create a generic type - that supports type parameter for view-model - which in turn can be used in Binding markup extension.

    Although it may not give you intellisense like support - but should definitely remove the warning for you.

    For e.g.:

    /// <summary>
    /// Create a base page with generic support
    /// </summary>
    public class ContentPage<T> : ContentPage
    {
        /// <summary>
        /// This property basically type-casts the BindingContext to expected view-model type
        /// </summary>
        /// <value>The view model.</value>
        public T ViewModel { get { return (BindingContext != null) ? (T)BindingContext : default(T); } }
    
        /// <summary>
        /// Ensure ViewModel property change is raised when BindingContext changes
        /// </summary>
        protected override void OnBindingContextChanged()
        {
            base.OnBindingContextChanged();
    
            OnPropertyChanged(nameof(ViewModel));
        }
    }
    

    Sample usage

    <?xml version="1.0" encoding="utf-8"?>
    <l:ContentPage 
        ...
        xmlns:l="clr-namespace:SampleApp" 
        x:TypeArguments="l:ThisPageViewModel"
        x:Name="This"
        x:Class="SampleApp.SampleAppPage">
    
        ...                            
             <Label Text="{Binding ViewModel.PropA, Source={x:Reference This}}" />
        ...
    </l:ContentPage>
    

    Code-behind

    public partial class SampleAppPage : ContentPage<ThisPageViewModel>
    {
        public SampleAppPage()
        {
            InitializeComponent();
    
            BindingContext = new ThisPageViewModel();
        }
    }
    

    View model

    /// <summary>
    /// Just a sample viewmodel with properties
    /// </summary>
    public class ThisPageViewModel
    {
        public string PropA { get; } = "PropA";
        public string PropB { get; } = "PropB";
        public string PropC { get; } = "PropC";
    
        public string[] Items { get; } = new[] { "1", "2", "3" };
    }