Search code examples
c#mvvmdata-bindingxamarin.formspropertychanged

Xamarin.Forms & MVVM In C#: PropertyChanged Event Handler Is Always Null When OnPropertyChanged Called


I've written a simple application using Xamarin.Forms in C# with Visual Studio 2017, and have a problem with data binding working to update the contents of a Label child control to display the selected date value from a DatePicker child control in the same ContentPage. Although all of the project elements compile, build and deploy without any errors, and the OnPropertyChanged method in the ViewModel is called each and every time when I change the date selected in the DatePicker, the PropertyChanged event handler is always a null object reference when the OnPropertyChanged method is called, and in turn, does not update the contents of the Label's Text property as expected with the new selected date value.

The following are my MainPage implementation in addition to the backing ViewModel class, SelectedDateViewModel.cs.

C# [MainPage.xaml.cs]

using System;
using Xamarin.Forms;

namespace DataBoundDatePicker
{
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();

            BindingContext = new SelectedDateViewModel();
        }
    }
}

XAML [MainPage.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:local="clr-namespace:DataBoundDatePicker"
             x:Class="DataBoundDatePicker.MainPage">
    <StackLayout Padding="20" VerticalOptions="Center" HorizontalOptions="Center">
        <Label Text="Data Bound DatePicker" FontAttributes="Bold" HorizontalOptions="Center" />
        <Label Text="Select a Date:" />
        <DatePicker x:Name="SelectedDatePicker" Date="{Binding SelectedDate}"/>
        <StackLayout Orientation="Horizontal">
            <Label Text="Formatted Date:" />
            <Label x:Name="FormattedDateLabel" Text="{Binding SelectedDate, StringFormat='{0:dddd, MMMM d, yyyy}'}" />
        </StackLayout>
    </StackLayout>
</ContentPage>

C# [SelectedDateViewModel.cs]

using System;
using System.ComponentModel;
using System.Diagnostics;

namespace DataBoundDatePicker
{
    public class SelectedDateViewModel
    {
        private readonly string FullDateFormat = "dddd, MMMM d, yyyy";

        private DateTime selectedDate;

        public event PropertyChangedEventHandler PropertyChanged;

        public SelectedDateViewModel()
        {
            Debug.WriteLine("Entering SelectedDateViewModel.SelectedDateViewModel() - Constructor");

            SelectedDate = DateTime.Now;

            Debug.WriteLine("Leaving SelectedDateViewModel.SelectedDateViewModel() - Constructor");
        }

        public DateTime SelectedDate
        {
            get
            {
                return selectedDate;
            }

            set
            {
                if (selectedDate != value)
                {
                    selectedDate = value;
                    OnPropertyChanged("SelectedDate");
                }
            }
        }

        protected virtual void OnPropertyChanged(string propertyName)
        {
            Debug.WriteLine("Inside SelectedDateViewModel.OnPropertyChanged()");

            Debug.WriteLine($"SelectedDate = {selectedDate.ToString(FullDateFormat)}");

            var trace =
            $"PropertyChanged Is Null: {(PropertyChanged == null ? "Yes" : "No")}";
            Debug.WriteLine(trace);

            var propertyChangedCallback = PropertyChanged;
            propertyChangedCallback?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

The current implementation does the following:

  • On startup, the FormattedDateLabel displays the correct formatted date string based on the SelectedDate DateTime property in the SelectedDateViewModel. However, any subsequent changes to the Date property in the SelectedDatePicker control.

  • Without fail, each and every time the Date value is changed in the SelectedDatePicker control, the SelectedDateViewModel.OnPropertyChanged() method is always called, even on startup through the instantiation of MainPage and SelectedDateViewModel object instances (e.g., constructor calls).

  • Although the SelectedDateViewModel.OnPropertyChanged() is called any time when the Date value is changed in the DatePicker, SelectedDateViewModel's PropertyChanged event reference is always null, even on startup (again, when instantiated through constructor calls). This is shown with the following Debug trace statements emitted as the program is running in Debug mode through Visual Studio:

'DataBoundDatePicker.UWP.exe' (CoreCLR: CoreCLR_UWP_Domain): Loaded 'c:\work\Xamarin\DataBoundDatePicker\DataBoundDatePicker\DataBoundDatePicker.UWP\bin\x86\Debug\AppX\Xamarin.Forms.Xaml.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled. Entering MainPage.MainPage() - Constructor Entering SelectedDateViewModel.SelectedDateViewModel() - Constructor Inside SelectedDateViewModel.OnPropertyChanged() SelectedDate = Tuesday, July 4, 2017 PropertyChanged Is Null: Yes Leaving SelectedDateViewModel.SelectedDateViewModel() - Constructor Leaving MainPage.MainPage() - Constructor The thread 0x1630 has exited with code 0 (0x0). Inside SelectedDateViewModel.OnPropertyChanged() SelectedDate = Sunday, March 4, 2012 PropertyChanged Is Null: Yes

  • The code exhibits the same behavior, regardless of deploying and debugging the UWP or Android version of the application.

Any help or insights and explanations you can share on how I can get the Text property to have its Text property bound to the Date property using the ViewModel where the PropertyChanged event is not a null reference would be greatly appreciated.

Thank you in advance for your time and help.


Solution

  • Use INotifyPropertyChanged Interface. like this.

    public class SelectedDateViewModel : INotifyPropertyChanged
        {
            private readonly string FullDateFormat = "dddd, MMMM d, yyyy";
    
            private DateTime selectedDate;
    
            public event PropertyChangedEventHandler PropertyChanged;
    
            public SelectedDateViewModel()
            {
                Debug.WriteLine("Entering SelectedDateViewModel.SelectedDateViewModel() - Constructor");
    
                SelectedDate = DateTime.Now;
    
                Debug.WriteLine("Leaving SelectedDateViewModel.SelectedDateViewModel() - Constructor");
            }
    
            public DateTime SelectedDate
            {
                get
                {
                    return selectedDate;
                }
    
                set
                {
                    if (selectedDate != value)
                    {
                        selectedDate = value;
                        OnPropertyChanged("SelectedDate");
                    }
                }
            }
    
            protected virtual void OnPropertyChanged(string propertyName)
            {
                 if (PropertyChanged != null)
                 {
                      PropertyChanged(this,
                             new PropertyChangedEventArgs(propertyName));
                 }
            }
        }