Saturday, July 28, 2018

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

Leave a Comment

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

1 Answers

Answers 1

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" }; } 
If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment