Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 439
TheGeneral Avatar asked Jul 19 '18 00:07

TheGeneral


1 Answers

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" };
}
like image 131
Sharada Gururaj Avatar answered Nov 07 '22 20:11

Sharada Gururaj