CustomView in ListView is not binding....

JuanJChiwJuanJChiw ESMember ✭✭

Hi! Can you please help me with this, is becoming quite a headache to do not understand what's happening after reading through the docs and the forum

I have this xaml where I show a Movie and it has Rates, so the rates I decided to create a custom view and display some stars.....

So this is the ContentPage

<ListView ItemsSource="{Binding Items}">
    <ListView.ItemTemplate>
        <DataTemplate>
            <ViewCell>
        <StackLayout>
                    <local:MyView  LabelRate="Happy" Rate="{Binding HappyRate}" />
                    <local:MyView  LabelRate="Sad" Rate="{Binding SadRate}" />
                     <local:MyView  LabelRate="Horror" Rate="{Binding HorrorRate}" />
        </StackLayout>
             </ViewCell>
        </DataTemplate>
  </ListView.ItemTemplate>
</ListView>

And this is the ContentView aka MyView

<ContentView>
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="45"></ColumnDefinition>
                <ColumnDefinition Width="*"></ColumnDefinition>
            </Grid.ColumnDefinitions>
            <Label Text="{Binding LabelRate}" FontSize="14" TextColor="#3c3947" Grid.Column="0" />
<!--  star behaviour  based in  https://blog.xamarin.com/behaviors-in-xamarin-forms/-->
            <StackLayout Orientation="Horizontal" Grid.Column="1">
            <Grid>
                <Grid.Behaviors>
                    <bh:StarBehavior x:Name="starOne" GroupName="{Binding LabelRate}" IsReadOnly="True" />
                </Grid.Behaviors>
                <ffimageloading:CachedImage x:Name="starBlankOne"
                        Source="star_outline.png" />

                <ffimageloading:CachedImage x:Name="starSelectedOne"
                        Source="star_selected.png"
                        IsVisible="{Binding Source={x:Reference starOne}, Path=IsStarred}" />
            </Grid>
            <Grid>
                <Grid.Behaviors>
                    <bh:StarBehavior x:Name="starTwo" GroupName="{Binding LabelRate}" IsReadOnly="True" />
                </Grid.Behaviors>
                <ffimageloading:CachedImage x:Name="starBlankTwo"
                        Source="star_outline.png" />

                <ffimageloading:CachedImage x:Name="starSelectedTwo"
                        Source="star_selected.png"
                        IsVisible="{Binding Source={x:Reference starTwo}, Path=IsStarred}" />
            </Grid>
            <Grid>
                <Grid.Behaviors>
                    <bh:StarBehavior x:Name="starThree" GroupName="{Binding LabelRate}" IsReadOnly="True" />
                </Grid.Behaviors>
                <ffimageloading:CachedImage x:Name="starBlankThree"
                        Source="star_outline.png" />

                <ffimageloading:CachedImage x:Name="starSelected"
                        Source="star_selected.png"
                        IsVisible="{Binding Source={x:Reference starThree}, Path=IsStarred}" />
            </Grid>
            <Grid>
                <Grid.Behaviors>
                    <bh:StarBehavior x:Name="starFour" GroupName="{Binding LabelRate}" IsReadOnly="True" />
                </Grid.Behaviors>
                <ffimageloading:CachedImage x:Name="starBlankFour"
                        Source="star_outline.png" />

                <ffimageloading:CachedImage x:Name="starSelectedFour"
                        Source="star_selected.png"
                        IsVisible="{Binding Source={x:Reference starFour}, Path=IsStarred}" />
            </Grid>
            <Grid>
                <Grid.Behaviors>
                    <bh:StarBehavior x:Name="starFive" GroupName="{Binding LabelRate}" IsReadOnly="True" />
                </Grid.Behaviors>
                <ffimageloading:CachedImage x:Name="starBlankFive"
                        Source="star_outline.png" />

                <ffimageloading:CachedImage x:Name="starSelectedFive"
                        Source="star_selected.png"
                        IsVisible="{Binding Source={x:Reference starFive}, Path=IsStarred}" />
            </Grid>
        </StackLayout>
            </Grid>
</ContentView>

And this is the code behind of MyView

public partial class MyView: ContentView
{
    public MyView()
    {
        InitializeComponent();
    }

    public static readonly BindableProperty LabelRateProperty =
        BindableProperty.Create("LabelRate", typeof(string), typeof(MyView), default(string));

    public string LabelRate
    {
        get { return (string)GetValue(LabelRateProperty); }
        set { SetValue(LabelRateProperty, value); }
    }

    public static readonly BindableProperty RateProperty =
        BindableProperty.Create("Rate", typeof(float), typeof(MyView), default(float), propertyChanged: OnRateChanged);

    public float Rate
    {
        get { return (float)GetValue(RateProperty); }
        set { SetValue(RateProperty, value); }
    }

    private static void OnRateChanged(BindableObject bindable, object oldValue, object newValue)
    {
        var obj = (MyView)bindable;
        var value = (float)newValue;

        obj.starOne.IsStarred = value >= 1;
        obj.starTwo.IsStarred = value >= 2;
        obj.starThree.IsStarred = value >= 3;
        obj.starFour.IsStarred = value >= 4;
        obj.starFive.IsStarred = value >= 5;
    }
}

And it doesn't work and I'm sad :(

The first step that I took was to set the BindingContext in the ctr of MyView to this

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

And it worked or that's that what I thought, this worked with the property LabelRate because the value was "hardcoded" LabelRate="Happy" but the binding Rate="{Binding HappyRate}" it just didn't work..... and I've tried with Rate="{Binding Path=HappyRate}" and other more crazy stuff...... If I hardcoded the value Rate="5" it worked.

The workaround that I'm taking is to not set the BindingContext to this and override OnBindingContextChanged

protected override void OnBindingContextChanged()
{
    base.OnBindingContextChanged();
        if (LabelRate == "Happy")
            {
                Rate = (BindingContext as Movie).HappyRate;
            }
            if (LabelRate == "Sad")
            {
                Rate = (BindingContext as Movie).SadRate;
            }
            if (LabelRate == "Horror")
            {
                Rate = (BindingContext as Movie).HorrorRate;
            }
    }

And that works because when I change the Rate the OnRateChanged is fired and I'm changing the Behaviour property..... but this breaks the Binding of the Label since LabelRate doesn't exist in the Movie class, I will named the label of the custom view and do something like this

protected override void OnBindingContextChanged()
{
    base.OnBindingContextChanged();
    xamlLabel.Text = LabelRate;
    if (LabelRate == "Happy")
    {
        Rate = (BindingContext as Movie).HappyRate;
    }
    if (LabelRate == "Sad")
    {
        Rate = (BindingContext as Movie).SadRate;
    }
    if (LabelRate == "Horror")
    {
        Rate = (BindingContext as Movie).HorrorRate;
    }
}

So even I managed somehow to do what I wanted, I'm sure that I'm doing something very wrong, because I think that this should work without the OnBindingContextChanged and all the quirky stuff I'm doing.......

Thanks for reading :)

Answers

  • GeraldVersluisGeraldVersluis NLUniversity ✭✭✭✭

    Shouldn't you also implement the INotifyPropetyChanged interface to raise an event that the value of properties actually changed and reporting that back to the view?

  • JuanJChiwJuanJChiw ESMember ✭✭

    I don't think so based on this answer https://forums.xamarin.com/discussion/37076/data-binding-in-my-custom-control it's not needed....

    The main problem without all the clutter is this.. I have a ContentView (MyView) that has a BindableProperty LabelRate that when I try to use <local:MyView LabelRate="{Binding Name}" /> it doesn't set the LabelRate and MyView does not render the value but if i use <local:MyView LabelRate="Happy" /> this set the BindableProperty and I can use the value. In order to use the value I have to change the BindingContext in the ctr to this because the BindingContext would be the Model of the list

    <!-- part of a contentpage -->
    <ListView ItemsSource="{Binding Items}">
        <ListView.ItemTemplate>
            <DataTemplate>
                <ViewCell>
            <StackLayout>
                        <local:MyView  LabelRate="{Binding Name}" />
            </StackLayout>
                 </ViewCell>
            </DataTemplate>
      </ListView.ItemTemplate>
    </ListView>
    
    <!-- The contentView aka MyView -->
    <ContentView>
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="45"></ColumnDefinition>
                <ColumnDefinition Width="*"></ColumnDefinition>
            </Grid.ColumnDefinitions>
            <Label Text="{Binding LabelRate}" FontSize="14" TextColor="#3c3947" Grid.Column="0" />
        </Grid>
    </ContentView>
    

    MyView.xaml.cs

    public partial class MyView: ContentView
        {
            public MyView()
            {
                InitializeComponent();
            BindingContext = this;
            }
    
            public static readonly BindableProperty LabelRateProperty =
                BindableProperty.Create("LabelRate", typeof(string), typeof(MyView), default(string));
    
            public string LabelRate
            {
                get { return (string)GetValue(LabelRateProperty); }
                set { SetValue(LabelRateProperty, value); }
            }
        }
    
Sign In or Register to comment.