Picker BindingContext does not inherites from ContentPage BindingContext

jddjdd USMember ✭✭✭

Hello,
First, I am surprised that my Pickers BindingContext inherites from their parent StackLayout but not from the ContentPage. Is that normal?
Second, a picker inherites from its parent StackLayout BindingContext after the SelectedIndex is set. How to set the SelectedIndex after the Picker has inherited from its parent BindingContext?

this is my XAML code:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="AlmicantaratXF.Views.SightPage"
             xmlns:i18n="clr-namespace:AlmicantaratXF.Views;assembly=AlmicantaratXF"
             xmlns:viewModels="clr-namespace:AlmicantaratXF.ViewModels;assembly=AlmicantaratXF"
             xmlns:converters="clr-namespace:AlmicantaratXF.Converters;assembly=AlmicantaratXF"
             xmlns:behaviors="clr-namespace:AlmicantaratXF.Behaviors"
             Title="{i18n:Translate titleSight}"
             BackgroundColor="{StaticResource AppBackgroundColor}">
    <!--   Title="{i18n:Translate titleSight}">     -->

    <ContentPage.BindingContext>
        <viewModels:SightPageViewModel/>
    </ContentPage.BindingContext>

    <ContentPage.Content>
        <StackLayout  Orientation="Vertical"
                      Margin ="0"
                      Padding="0">

<!--  If the StackLayout.BindingContext is commented out, the Pickers binding does not work
        as it is not commented out, the SightPAgeViewModel constructor is executed twice: one by ContentPage, second by StackLayout -->

            <StackLayout.BindingContext>
                <viewModels:SightPageViewModel/>
            </StackLayout.BindingContext>

                <Picker
                    x:Name="PickerBodyType" 
                        Title="Body Type"
                        ItemsSource="{Binding BodyTypes, Mode=TwoWay}"
                        SelectedIndex="1"
                        WidthRequest="120"
                        Margin="0">
                    <!--<Picker.BindingContext>
                        <viewModels:SightPageViewModel/>
                    </Picker.BindingContext>-->
                </Picker>

                <Picker 
                        x:Name="PickerLimb"
                        Title="limb"
                        WidthRequest="120"
                        Margin="0"
                        ItemsSource="{Binding Limb, Mode=TwoWay}"
                        SelectedIndex="0"
                        IsVisible="{Binding Source ={x:Reference PickerBodyType},
                                           Path=SelectedIndex,
                                           Converter= {StaticResource selectedItemToBool},
                                           ConverterParameter=1}">
                </Picker>

                <Picker 
                        Title="planets"   
                        WidthRequest="120"
                        Margin="0"
                        ItemsSource="{Binding Planets, Mode=TwoWay}"
                        SelectedIndex="0"
                        IsVisible="{Binding Source ={x:Reference PickerBodyType},
                                           Path=SelectedIndex,
                                           Converter= {StaticResource selectedItemToBool},
                                           ConverterParameter=2}" >
                </Picker>

                <Picker
                        Title="stars"   
                        WidthRequest="120" 
                        Margin="0"
                        ItemsSource="{Binding StarsCollection, Mode=TwoWay}"
                        SelectedIndex="0"
                        IsVisible="{Binding Source ={x:Reference PickerBodyType},
                                           Path=SelectedIndex,
                                           Converter= {StaticResource selectedItemToBool},
                                           ConverterParameter=3}" >
                </Picker>

        </StackLayout>
    </ContentPage.Content>
</ContentPage>

Best Answers

  • jddjdd US ✭✭✭
    Accepted Answer

    Hi, I have found out the problem.
    I have a page that contains the list of the sights. When the user click on a sight, the app PushAsync the SightPage.
    But I was binding the SightPage to the model!

    ListOfSightsPage.xaml.cs

            async void OnSightSelected(object sender, SelectedItemChangedEventArgs e)
            {
                //déselectionner la ligne pour qu'elle ne soit plus en surbrillance
                (sender as ListView).SelectedItem = null;
                if (e.SelectedItem != null)
                {
                    await Navigation.PushAsync(new SightPage
                    {
                        BindingContext = e.SelectedItem as Sight    // WRONG: Sight is the Model, not the ViewModel
                    });
                }
            }
    

    Instead, I have to bind the SightPage to the ViewModel:

    ListOfSightsPage.xaml.cs

                    await Navigation.PushAsync(new SightPage(e.SelectedItem as Sight));
    

    SightPage.xaml.cs

        public partial class SightPage : ContentPage
        {
            public SightPage (Sight currentSight)
            {
                InitializeComponent ();
                BindingContext = new SightPageViewModel(currentSight); // THIS SEEMS BETTER
            }
    ...etc
    

    Thank you @LandLu for your help. You were right, there was a root cause you could not see in my XAML code.

Answers

  • LandLuLandLu Member, Xamarin Team Xamurai

    First, I am surprised that my Pickers BindingContext inherites from their parent StackLayout but not from the ContentPage. Is that normal?

    Yes, this is how binding works. It will retrieve the lowest part's binding context, for example, its own binding context. If binding can't get the binding context then upper iterate to check the parent controls' one by one. The parent stacklayout's binding context will be obtained earlier than the content page which aligns in a higher hierarchy.

    Second, a picker inherites from its parent StackLayout BindingContext after the SelectedIndex is set. How to set the SelectedIndex after the Picker has inherited from its parent BindingContext?

    Create an int property in your view model for binding:

    int currentIndex;
    public int CurrentIndex
    {
        set
        {
            currentIndex = value;
            onPropertyChanged();
        }
        get => currentIndex;
    }
    
  • jddjdd USMember ✭✭✭
    OK, but when the StackLayout BindingContext is commented out, the picker does not iterate upper to get the ContentPage Binding Context: that is the issue...
  • jddjdd USMember ✭✭✭

    This is the full code: .xaml - .xaml.cs - ViewModel
    I have just built and debugged it: the Picker x:Name="PickerBodyType" is not bound to BodyTypes (it is not populated).

    .xaml

    <?xml version="1.0" encoding="utf-8" ?>
    <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                 x:Class="AlmicantaratXF.Views.SightPage"
                 xmlns:i18n="clr-namespace:AlmicantaratXF.Views;assembly=AlmicantaratXF"
                 xmlns:viewModels="clr-namespace:AlmicantaratXF.ViewModels;assembly=AlmicantaratXF"
                 xmlns:converters="clr-namespace:AlmicantaratXF.Converters;assembly=AlmicantaratXF"
                 xmlns:behaviors="clr-namespace:AlmicantaratXF.Behaviors"
                 Title="{i18n:Translate titleSight}"
                 BackgroundColor="{StaticResource AppBackgroundColor}">
        <!--   Title="{i18n:Translate titleSight}">     -->
        <ContentPage.Padding>
            <OnPlatform x:TypeArguments="Thickness">
                <On Platform="iOS" Value="10, 20, 10, 5" />
                <On Platform="Android, UWP" Value="10, 0, 10, 5" />
            </OnPlatform>
        </ContentPage.Padding>
    
        <ContentPage.BindingContext>
            <viewModels:SightPageViewModel/>
        </ContentPage.BindingContext>
    
        <ContentPage.Resources>
            <ResourceDictionary>
                <Style TargetType="Entry" >
                    <Setter Property="Keyboard" Value="Numeric" />
                </Style>
                <converters:SelectedItemToBool x:Key="selectedItemToBool" />
            </ResourceDictionary>
        </ContentPage.Resources>
        <ContentPage.ToolbarItems>
            <ToolbarItem Text="{i18n:Translate buttonDelete}" Clicked="OnDelete" />
        </ContentPage.ToolbarItems>
        <ContentPage.Content>
            <StackLayout  Orientation="Vertical"
                          Margin ="0"
                          Padding="0">
                <!--<StackLayout.BindingContext>
                    <viewModels:SightPageViewModel/>
                </StackLayout.BindingContext>-->
                <StackLayout Orientation="Horizontal"
                             Margin ="0"
                             Padding="0"
                             HorizontalOptions="FillAndExpand" >
                    <Label Text="{i18n:Translate labelBodyType}"
                           WidthRequest="110"
                           Margin="0"/>
                    <Picker
                        x:Name="PickerBodyType" 
                            Title="Body Type"
                            ItemsSource="{Binding BodyTypes, Mode=TwoWay}"
                            SelectedIndex="1"
                            WidthRequest="120"
                            Margin="0">
                        <!--<Picker.BindingContext>
                            <viewModels:SightPageViewModel/>
                        </Picker.BindingContext>-->
                    </Picker>
                </StackLayout>
                <StackLayout Orientation="Horizontal"
                             Margin ="0"
                             Padding="0"
                             HorizontalOptions="FillAndExpand" >
                    <Label Text="{i18n:Translate limb}"
                           WidthRequest="110"
                           Margin="0"
                           BindingContext="{x:Reference Name=PickerBodyType}"
                           IsVisible="{Binding Source ={x:Reference PickerBodyType},
                                               Path=SelectedIndex,
                                               Converter= {StaticResource selectedItemToBool},
                                               ConverterParameter=1}" />
                    <Picker 
                            x:Name="PickerLimb"
                            Title="limb"
                            WidthRequest="120"
                            Margin="0"
                            ItemsSource="{Binding Limb, Mode=TwoWay}"
                            SelectedIndex="0"
                            IsVisible="{Binding Source ={x:Reference PickerBodyType},
                                               Path=SelectedIndex,
                                               Converter= {StaticResource selectedItemToBool},
                                               ConverterParameter=1}">
                    </Picker>
                </StackLayout>
                <StackLayout Orientation="Horizontal"
                             Margin ="0"
                             Padding="0"
                             HorizontalOptions="FillAndExpand" >
                    <Label Text="{i18n:Translate planet}"
                           WidthRequest="110" 
                           Margin="0"
                           IsVisible="{Binding Source ={x:Reference PickerBodyType},
                                               Path=SelectedIndex,
                                               Converter= {StaticResource selectedItemToBool},
                                               ConverterParameter=2}" />
                    <Picker 
                            Title="planets"   
                            WidthRequest="120"
                            Margin="0"
                            ItemsSource="{Binding Planets, Mode=TwoWay}"
                            SelectedIndex="0"
                            IsVisible="{Binding Source ={x:Reference PickerBodyType},
                                               Path=SelectedIndex,
                                               Converter= {StaticResource selectedItemToBool},
                                               ConverterParameter=2}" >
                    </Picker>
                </StackLayout>
                <StackLayout Orientation="Horizontal"
                             Margin ="0"
                             Padding="0"
                             HorizontalOptions="FillAndExpand" >
                    <Label Text="{i18n:Translate star}" 
                           WidthRequest="110" 
                           Margin="0"
                           IsVisible="{Binding Source ={x:Reference PickerBodyType},
                                               Path=SelectedIndex,
                                               Converter= {StaticResource selectedItemToBool},
                                               ConverterParameter=3}" />
                    <Picker
                            Title="stars"   
                            WidthRequest="120" 
                            Margin="0"
                            ItemsSource="{Binding StarsCollection, Mode=TwoWay}"
                            SelectedIndex="0"
                            IsVisible="{Binding Source ={x:Reference PickerBodyType},
                                               Path=SelectedIndex,
                                               Converter= {StaticResource selectedItemToBool},
                                               ConverterParameter=3}" >
                    </Picker>
                </StackLayout>
                <StackLayout Orientation="Horizontal" HorizontalOptions="FillAndExpand" Padding="0">
    
                    <Label Text="{i18n:Translate labelCurrentTime}"
                        WidthRequest="140"/>
                    <Label Text="{Binding DateTimeString}"
                        TextColor="{StaticResource LabelInfoTextColor}">
                        <Label.BindingContext>
                            <viewModels:ClockViewModel />
                        </Label.BindingContext>
                    </Label>
                </StackLayout>
                <StackLayout Orientation="Horizontal" HorizontalOptions="FillAndExpand" Padding="0">
                    <Label Text="{i18n:Translate lat}"
                           WidthRequest="140" />
                    <Label Text="{i18n:Translate lon}" />
                </StackLayout>
                <StackLayout Orientation="Horizontal"
                                                 Margin ="0"
                                                 Padding="0"
                                                 HorizontalOptions="FillAndExpand" >
                    <Label x:Name="labelLat"
                           Text="{Binding StrLat}"      
                           TextColor="{StaticResource LabelInfoTextColor}"
                           WidthRequest="140"
                           Margin="0">
                        <Label.BindingContext>
                            <viewModels:GeoLocationUpdateViewModel />
                        </Label.BindingContext>
                    </Label>
                    <Label x:Name="labelLon"
                           Text="{Binding StrLon}"
                           TextColor="{StaticResource LabelInfoTextColor}"
                           WidthRequest="150"
                           Margin="0">
                        <Label.BindingContext>
                            <viewModels:GeoLocationUpdateViewModel />
                        </Label.BindingContext>
                    </Label>
                </StackLayout>
                <Button Text="{i18n:Translate buttonNow}"
                        Clicked="OnNowClicked"
                        />
                <StackLayout Orientation="Horizontal"
                        Margin ="0"
                        Padding="0"
                        HorizontalOptions="FillAndExpand">
                    <Label Text="{i18n:Translate labelSightTime}"
                        WidthRequest="140"/>
                    <Label x:Name="labelSightTime"
                        Text="-"
                        TextColor="{StaticResource LabelInfoTextColor}"/>
                </StackLayout>
                <StackLayout Orientation="Horizontal" HorizontalOptions="FillAndExpand" Padding="0">
                    <Label Text="{i18n:Translate lat}" WidthRequest="140" />
                    <Label Text="{i18n:Translate lon}" />
                </StackLayout>
                <StackLayout Orientation="Horizontal"
                                                     Margin ="0"
                                                     Padding="0"
                                                     HorizontalOptions="FillAndExpand" >
                    <Label x:Name="labelSightLat"
                               Text="-"      
                               TextColor="{StaticResource LabelInfoTextColor}"
                               WidthRequest="140"
                               Margin="0">
                    </Label>
                    <Label x:Name="labelSightLon"
                               Text="-"
                               TextColor="{StaticResource LabelInfoTextColor}"
                               WidthRequest="150"
                               Margin="0">
                    </Label>
                </StackLayout>
                <Label 
                       Text="{i18n:Translate labelAltitude}"
                       HorizontalOptions="Center"
                       VerticalOptions="Center"/>
                <Entry Text="">
                    <Entry.Behaviors>
                        <behaviors:AltitudeEntryBehavior/>
                    </Entry.Behaviors>
                </Entry>
            </StackLayout>
        </ContentPage.Content>
    </ContentPage>
    

    xaml.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Resources;
    
    using Xamarin.Forms;
    using Xamarin.Forms.Xaml;
    
    using AlmicantaratXF.Resources;
    using AlmicantaratXF.ViewModels;
    using AlmicantaratXF.Model;
    
    namespace AlmicantaratXF.Views
    {
        [XamlCompilation(XamlCompilationOptions.Compile)]
        public partial class SightPage : ContentPage
        {
            private ResourceManager rm = Strings.ResourceManager;
            public SightPage ()
            {
                InitializeComponent ();
                //PickerBodyType.SelectedIndex = 0;
            }
            protected override async void OnAppearing()
            {
                base.OnAppearing();
                /*Sight currentSight = BindingContext as Sight;
                switch(currentSight.StarID)
                {
                    case 0: //Soleil
                        PickerBodyType.SelectedIndex = 0;
                        if (currentSight.LowerLimb)
                            PickerLimb.SelectedIndex = 0;
                        else
                            PickerLimb.SelectedIndex = 1;
                        break;
    
                }*/
            }
            protected override async void OnDisappearing()
            {
                base.OnDisappearing();
            }
            async void OnNowClicked(object sender, EventArgs args)
            {
                labelSightTime.Text = DateTime.Now.ToString();
                Position position;
                position = (labelLat.BindingContext as GeoLocationUpdateViewModel).Position;
                labelSightLat.Text = position.StrLat;
                labelSightLon.Text = position.StrLon;
            }
            async void OnDelete(object sender, EventArgs args)
            {
                bool answer = await DisplayAlert(
                    rm.GetString("warning"),
                    rm.GetString("messageDeleteSight"),
                    rm.GetString("deleteAnyway"),
                    rm.GetString("cancel"));
                //System.Diagnostics.Debug.WriteLine("Answer: " + answer);
                if (answer)
                {
                    Model.Sight currentSight = BindingContext as Model.Sight;
                    await AlmicantaratXF.Views.App.PositionsSightsDB.DeleteSightAsync(currentSight);
                    await Navigation.PopAsync();
                }
            }
        }
    }
    

    SightPageViewModel.cs

    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Text;
    using System.Resources;
    
    using AlmicantaratXF.Resources;
    using AlmicantaratXF.Views;
    
    namespace AlmicantaratXF.ViewModels
    {
        class SightPageViewModel : BaseViewModel
        {
            private ResourceManager rm = Strings.ResourceManager;
    
            public IList<string> BodyTypes { get; private set; }
            public IList<string> Limb { get; private set; }
            public IList<string> Planets { get; private set; }
            public IList<string> StarsCollection { get; private set; }
            private SortedDictionary<string, int> Stars;
            public SightPageViewModel()
            {
                BodyTypes = new List<string>
                {
                    rm.GetString("sun"),
                    rm.GetString("moon"),
                    rm.GetString("planet"),
                    rm.GetString("star")
                };
    
                Limb = new List<string>
                {
                    rm.GetString("lower"),
                    rm.GetString("upper")
                };
    
                Planets = new List<string>
                {
                    rm.GetString("Mercury"),
                    rm.GetString("Venus"),
                    rm.GetString("Mars"),
                    rm.GetString("Jupiter"),
                    rm.GetString("Saturn")
                };
                var ci = System.Globalization.CultureInfo.CurrentCulture;
                Stars = new SortedDictionary<string,int>();
                Stars.Add(App.StarsNames[32349 ].Designation(ci),32349);
                Stars.Add(App.StarsNames[30438 ].Designation(ci),30438 );
                Stars.Add(App.StarsNames[69673 ].Designation(ci),69673 );
    //etc   
                StarsCollection = new List<string>(Stars.Keys);
            }
            public int GetHIP(string starname)
            {
                return Stars[starname];
            }
        }
    }
    
    
  • LandLuLandLu Member, Xamarin Team Xamurai

    It looks like your picker's source has been set to the content page's binding context.
    And the source should be displayed.
    Could you reproduce it in a blank project? I need to run the sample to check other references.

  • jddjdd USMember ✭✭✭

    Hello,

    I could not even create a blank project: VS19 exited with an error. I guess I have to update it.
    I went around my problem like that:

        public partial class SightPage : ContentPage
        {
            private ResourceManager rm = Strings.ResourceManager;
            public SightPage ()
            {
                InitializeComponent ();
                PickerBodyType.BindingContext = BindingContext;
                PickerBodyType.SelectedIndex = 0;
            }
    

    It is not clean but it works: the SightPageViewModel constructor is executed only once.

  • LandLuLandLu Member, Xamarin Team Xamurai

    This is an approach which forces to set the Picker's binding context.
    However, if you used the code-behind to assign the binding context you could directly configure the content page's binding context in the code behind too.
    If you can't open the vs properly the environment could be broken. I suggest you repairing the ide and try again.

  • jddjdd USMember ✭✭✭
    Accepted Answer

    Hi, I have found out the problem.
    I have a page that contains the list of the sights. When the user click on a sight, the app PushAsync the SightPage.
    But I was binding the SightPage to the model!

    ListOfSightsPage.xaml.cs

            async void OnSightSelected(object sender, SelectedItemChangedEventArgs e)
            {
                //déselectionner la ligne pour qu'elle ne soit plus en surbrillance
                (sender as ListView).SelectedItem = null;
                if (e.SelectedItem != null)
                {
                    await Navigation.PushAsync(new SightPage
                    {
                        BindingContext = e.SelectedItem as Sight    // WRONG: Sight is the Model, not the ViewModel
                    });
                }
            }
    

    Instead, I have to bind the SightPage to the ViewModel:

    ListOfSightsPage.xaml.cs

                    await Navigation.PushAsync(new SightPage(e.SelectedItem as Sight));
    

    SightPage.xaml.cs

        public partial class SightPage : ContentPage
        {
            public SightPage (Sight currentSight)
            {
                InitializeComponent ();
                BindingContext = new SightPageViewModel(currentSight); // THIS SEEMS BETTER
            }
    ...etc
    

    Thank you @LandLu for your help. You were right, there was a root cause you could not see in my XAML code.

  • LandLuLandLu Member, Xamarin Team Xamurai

    I'm glad you found the root cause finally. Please mark the helpful answer to finish this discussion.

Sign In or Register to comment.