0

I have a simple app with a slider and a label. when I move the slider I can see the setter(Points) being called and updating the value, but when calling PropertyChanged the label doesn't update.

What I am expecting it to do is to call the PointsString getter when PropertyChanged is called. Using the debugger I have confirmed that ProperyChanged is being called and that the value of points is being updated. Why is the label bound to PointsString not being updated when the points setter is called?

View.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"
             xmlns:vm="clr-namespace:MyFirstApp.ViewModels"
             x:Class="MyFirstApp.Views.DetailPage">
    <ContentPage.Content>
       <TableView Intent="Form">
           <TableRoot>
               <TableSection Title="Name">
                   <EntryCell Label="Name:" />
                   </TableSection>
                   <TableSection Title="Rating">
                    <ViewCell>
                        <StackLayout Orientation="Horizontal" >
                            <Label Text="Points:"  />
                            <Label Text="{Binding PointsString}">
                                <Label.BindingContext>
                                    <vm:DetailsPageViewModel />
                                </Label.BindingContext>
                            </Label>

                        </StackLayout>
                    </ViewCell> 
                   <ViewCell>
                       <Grid>
                            <Slider Maximum="10" Value="{Binding Points , Mode=TwoWay}">
                                <Slider.BindingContext>
                                    <vm:DetailsPageViewModel/>
                                </Slider.BindingContext>
                            </Slider>
                        </Grid>
                   </ViewCell>
               </TableSection>
           </TableRoot>
       </TableView>
    </ContentPage.Content>
</ContentPage>

ModelView:

using System;
using System.ComponentModel;

    class DetailsPageViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private double points;

        public DetailsPageViewModel()
        {
            points = 4.0;
        }

        public DetailsPageViewModel(double Points) :  this()
        {
            points = Points;
        }

        public double Points
        {
            get { return points; }
            set
            {
                Console.WriteLine(value);
                points = value;

                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs("PointsString"));
                }

            }
        }

        public string PointsString
        {
            // Lazy load from points value
            get { return points.ToString(); }
        }
    }
}
geminiCoder
  • 2,918
  • 2
  • 29
  • 50
  • 1
    Is this method `PropertyChangedEventArgs` defined somewhere? Do you have any error in the debug logs? – Cfun Dec 04 '20 at 16:59
  • `PropertyChangedEventArgs` is defined in `System.ComponentModel` which is imported at the top -- I will update the code above to include the imports. No errors in the logs, which is why I am struggling with this. – geminiCoder Dec 04 '20 at 17:34
  • Does this answer your question? [Implementing INotifyPropertyChanged - does a better way exist?](https://stackoverflow.com/questions/1315621/implementing-inotifypropertychanged-does-a-better-way-exist) – Cfun Dec 04 '20 at 17:38

2 Answers2

1

Looking at your XAML I suspect that you are creating new view models and not binding to the single view model.

To give some context each time you declare this <vm:DetailsPageViewModel /> you are in fact creating a new instance of your view model.

You can try using a Relative binding.

Essentially you need to change this:

<Label Text="{Binding PointsString}">
    <Label.BindingContext>
        <vm:DetailsPageViewModel />
    </Label.BindingContext>
</Label>

to something like this:

<Label Text="{Binding Source={RelativeSource AncestorType={x:Type vm:DetailsPageViewModel}}, Path=PointsString}" />

To prove my point you could place a breakpoint in the constructor to your DetailsPageViewModel and I expect it to hit multiple times.

Bijington
  • 3,661
  • 5
  • 35
  • 52
  • 1
    or just use a single VM for the entire page – Jason Dec 04 '20 at 19:45
  • 1
    @Jason that is the result I am hoping they get to. I was assuming the declarations in the OPs post show they are trying to bind back to what they believe is a single VM – Bijington Dec 04 '20 at 20:01
  • Thanks @Bijington you were quite right I was creating two instances of my view model. I wasn't able to get you code above to work, but tried simply setting the `BindContext` of the Content Page, and it solved the problem. – geminiCoder Dec 04 '20 at 20:44
0

Thanks to the Answer by Bijington above -- I was instantiating two instances of the VM. The answer was to set the Binding context of the page and remove the specific BindingContext's

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:vm="clr-namespace:MyFirstApp.ViewModels"
             x:Class="MyFirstApp.Views.DetailPage"
             >
    <ContentPage.BindingContext>
        <vm:DetailsPageViewModel/>
    </ContentPage.BindingContext>
    <ContentPage.Content>
       <TableView Intent="Form">
           <TableRoot>
               <TableSection Title="Name">
                   <EntryCell Label="Name:" />
                   </TableSection>
                   <TableSection Title="Rating">
                    <ViewCell>
                        <StackLayout Orientation="Horizontal" >
                            <Label Text="Points:"  />
                            <Label Text="{Binding PointsString}" />
                        </StackLayout>
                    </ViewCell> 
                   <ViewCell>
                       <Grid>
                            <Slider Maximum="10" Value="{Binding Points , Mode=TwoWay}" />
                        </Grid>
                   </ViewCell>
               </TableSection>
           </TableRoot>
       </TableView>
    </ContentPage.Content>
</ContentPage>
geminiCoder
  • 2,918
  • 2
  • 29
  • 50