Binding a child view's property to the parent's model variables

I have a Page JourneyPage, that has a viewmodel JourneyViewModel set as the bindingcontext

public JourneyPage (JourneyModel journey)
{
    InitializeComponent ();
    BindingContext = model = new JourneyViewModel(journey);
}

I've also created a custom view GridButton to be displayed several times at the top of the JourneyPage

public partial class GridButton : ContentView
{
    public static readonly BindableProperty TextProperty = BindableProperty.Create(nameof(Text), typeof(string), typeof(GridButton), string.Empty, BindingMode.OneTime);
    public string Text { get { return (string)GetValue(TextProperty); } set { SetValue(TextProperty, value); } }

    public static readonly BindableProperty ShowNormalProperty = BindableProperty.Create(nameof(ShowNormal), typeof(bool), typeof(GridButton), true, BindingMode.OneWay);
    public bool ShowNormal { get { return (bool)GetValue(ShowNormalProperty); } set { SetValue(ShowNormalProperty, value); } }

        public GridButton ()
    {
        InitializeComponent ();
        BindingContext = this;
    }
}

This has two Properties, the string Text to be show in the button, and a boolean that triggers use to set the text color.
The buttons work just fine if I just hardcode the values into the xaml like so:

<layouts:GridButton
    x:Name="StartJourneyButton"
    ShowNormal="False"
    Text="Start">
</layouts:GridButton>

But I need my ShowNormal binding to represent the state of the model behind the JourneyPage, and I can't get this to work.
AlwaysFalse is defined in the JourneyViewModel behind JourneyPage as public bool AlwaysFalse { get { return false; } }

<layouts:GridButton
    x:Name="StartJourneyButton"
    ShowNormal="{Binding AlwaysFalse}"
    Text="Start">
</layouts:GridButton>

I have tried a bunch of different combinations of Path and Source to try and pass the result of the parent's model's property to the child view but as far as I can tell, the ShowNormal="{Binding AlwaysFalse}" is trying to use a property of the child view's BindingContext called AlwaysFalse rather than passing through the parent's result.
I think setting the bindingcontext of the gridbutton to the parent's model would allow it to use the AlwaysFalse property but then I wouldn't be able to define Text="Start"?
Very lost here, any help would be appreciated.

Answers

  • LandLuLandLu Member, Xamarin Team Xamurai

    You could point out the ShowNormal's source individually:

    <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                 xmlns:local="clr-namespace:Demo"
                 x:Class="Demo.MainPage"
                 Title="MainPage"
                 x:Name="Page">
    
        <StackLayout>
            <layouts:GridButton
                x:Name="StartJourneyButton"
                ShowNormal="{Binding BindingContext.AlwaysFalse, Source={x:Reference Page}}"
                Text="Start">
            </layouts:GridButton>
        </StackLayout>
    
    </ContentPage>
    

    But what I want to recommend is another manner. You must have bound your custom view's button's text to the public string Text, I suggest that you'd better not do this.
    If you want to set the button's text depending on the Text property, you could use bindable property's propertyChanged event:

    public static readonly BindableProperty TextProperty = BindableProperty.Create(nameof(Text), typeof(string), typeof(CustomView), string.Empty, BindingMode.OneWay,
                                                                                    propertyChanged: (bindableObject, oldValue, newValue) =>
                                                                                    {
                                                                                        var customView = bindableObject as CustomView;
                                                                                        customView.MyButton.Text = (string)newValue;
                                                                                    });
    public string Text { get { return (string)GetValue(TextProperty); } set { SetValue(TextProperty, value); } }
    
    public static readonly BindableProperty ShowNormalProperty = BindableProperty.Create(nameof(ShowNormal), typeof(bool), typeof(CustomView), true, BindingMode.OneWay,
                                                                                            propertyChanged: (bindableObject, oldValue, newValue) =>
                                                                                            {
                                                                                                var customView = bindableObject as CustomView;
                                                                                                customView.MyButton.IsEnabled = (bool)newValue;
                                                                                            });
    public bool ShowNormal { get { return (bool)GetValue(ShowNormalProperty); } set { SetValue(ShowNormalProperty, value); } }
    

    In this way, you could construct your custom view without binding:

    <ContentView xmlns="http://xamarin.com/schemas/2014/forms" 
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                 x:Class="Demo.CustomView">
      <ContentView.Content>
            <StackLayout>
                <Button x:Name="MyButton" />
            </StackLayout>
      </ContentView.Content>
    </ContentView>
    

    So that you don't have to set the ContentView's BindingContext to itself:

    public GridButton ()
    {
        InitializeComponent();
    }
    

    At last, you could use the content page's binding context directly in your content page:

    <layouts:GridButton
        x:Name="StartJourneyButton"
        ShowNormal="{Binding AlwaysFalse}"
        Text="Start">
    </layouts:GridButton>
    
Sign In or Register to comment.