Search code examples
c#wpfdata-bindingmvvm-lightinotifypropertychanged

MVVMLight Toolkit changes not reflected when property is changed


I'm using the MVVMLight Toolkit in my WPF project. All my ViewModels derive from the toolkit's ViewModelBase class, which implements the INotifyPropertyChanged for you and does all the notify work.

My current setup is extremely simple. I have a Person class with a single Name property.

public class Person
{
    public string Name { get; set; }
}

My window has a TextBlock and a Button, and to the TextBlock I bind the Name property of the Person class object that I have. DataContext is set using a ViewModelLocator class.

<Window x:Class="BindingTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:ignore="http://www.galasoft.ch/ignore"
        mc:Ignorable="d ignore"
        Height="300" Width="300"
        Title="MVVM Light Application"
        DataContext="{Binding Main, Source={StaticResource Locator}}">
    <Grid x:Name="LayoutRoot">
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <TextBlock Grid.Row="0" HorizontalAlignment="Center" VerticalAlignment="Center"
                   Text="{Binding Contact.Name}"/>
        <Button Grid.Row="1" Content="Click" Command="{Binding ClickCommand}"/>
    </Grid>
</Window>

In my ViewModel, I set the Name to Tom in the constructor, and change it when the button is clicked. I expect Tom to show up in the TextBlock when window is loaded (which it does), and to be changed to Jane when the button is clicked (which it doesn't).

public class MainViewModel : ViewModelBase
{
    private Person _contact = new Person();
    public Person Contact
    {
        get { return _contact; }
        set { Set(ref _contact, value); }
    }

    public RelayCommand ClickCommand { get; private set; }

    public MainViewModel(IDataService dataService)
    {
        Contact = new Person() { Name = "Tom" };
        ClickCommand = new RelayCommand(Click);
    }

    public void Click()
    {
        Contact.Name = "Jane";
    }
}

What am I missing?


Solution

  • Setting Contact.Name does not trigger the INotifyPropertyChanged.NotifyChanged event as the Contact setter is not executed. You could fix this using by one of the following techniques:

    Implement INotifyPropertyChanged also in your model class

    public class Person : INotifyPropertyChanged
    {
        private string _name;
    
        public string Name 
        { 
           get => _name; 
           set
           {
               _name = value;
               if (PropertyChanged != null)
                   PropertyChanged(this, nameof(Name)); 
           }
        }
    
        public event PropertyChangedHandler PropertyChanged;
    }
    

    Or wrap the PersonClass in a PersonViewModel

    public class PersonViewModel : ViewModelBase
    {
        private readonly Person _person;
    
        public PersonViewModel(Person person)
        {
            _person = person;
        }
    
        public string Name 
        { 
           get => _person.Name; 
           set
           {
                var name = _person.Name;
                if (Set(ref name, value))
                    _person.Name = name;
           }
        }
    }
    

    and in MainViewModel:

    private PersonViewModel _contactViewModel
    public PersonViewModel Contact
    {
        get { return _contactViewModel ?? (_contactViewModel = new PersonViewModel(_contact)); }
    }
    

    Or create a separate ContactName property in the MainViewModel

    ... and using ContactName instead of Contact.Name in the binding and the Click event handler.

    public string ContactName
    {
        get { return _contact.Name; }
        set 
        {
            var name = _contact.Name;
            if (Set(ref name, value))
                _contact.Name = name;
        }
    }