Search code examples
c#wpfdata-bindinginotifypropertychangeddatacontext

Assigning an object to another doesn't raise PropertyChanged event in WPF


I have tried to isolate my problem in a very simple (and working) project. Let's say I have a simple model with 2 fields, and I have configured its properties and a PropertyChanged event. This is a simplified implementation of my model, please notice the last method just gets a new client object from a list and returns it:

public class Clients : INotifyPropertyChanged
{
    private long _Id = 0;

    public long Id 
    {
        get { return this._Id; } 
        set { 
            this._Id = value;
            RaisePropertyChanged("Id");
        } 
    }

    private String _Name = string.Empty;

    public String Name 
    {
        get { return this._Name; }
        set { 
            if (value is null) 
                this._Name = string.Empty;
            else
                this._Name = value;
            RaisePropertyChanged("Name");
        } 
    }

    public event PropertyChangedEventHandler? PropertyChanged;

    internal void RaisePropertyChanged(string name)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
    }

    // Let's say this is a populated list with 100 clients with different ids and names
    public static List<Clients> clients = new List<Clients>();
    // ...method for loading the list with DB data

    public static Clients? LoadClient(long id)
    {
        return clients.FirstOrDefault(x => x.Id == id);
    }
}

And I have a simple .xaml file in order to display one client data (id and name). Each UI control has a binding with the client property and the trigger for updating. I have also 2 configured buttons that will make changes to the client values:

<TextBox x:Name="txtClientId" Text="{Binding Id, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
</TextBox>
<TextBox x:Name="txtClientName" Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
</TextBox>
<Button Name="btnNext" Click="btnNext_Click">Next</Button>
<Button Name="btnRandom" Click="btnRandom_Click">Random</Button>

Now let's take a look to my .xaml.cs code. Here is the initializing and the code for the 2 buttons: one of them just modify the client properties in the DataContext object directly, and the other just assigns a new client object to the DataContext client:

public partial class ClientsPage : Page
{
    private Models.Clients? client = null;

    public ClientsPage()
    {
        InitializeComponent();

        // Load all clients data in Models.Clients.clients list...
        client = new Models.Clients();

        DataContext = client;
    }
    
    private void btnNext_Click(object sender, RoutedEventArgs e)
    {
        client = Models.Clients.LoadClient(client.Id + 1);
    }

    private void btnRandom_Click(object sender, RoutedEventArgs e)
    {
        Random random = new Random();
        client.Id = random.Next(999);
        client.Name = "RANDOM";
    }
}
  • When I set the DataContext, data is displayed correctly in the UI -> OK
  • When I make changes directly in the properties of the client object changes are reflected correctly in the UI -> OK
  • But... when I load a new client object, and assign it to the variable set as DataContext, UI doesn't change since the PropertyChanged event is not raised.

Of course this situation can be solved by assigning property values instead of assigning a new object directly:

private void btnNext_Click(object sender, RoutedEventArgs e)
{
    Models.Clients newClient = Models.Clients.LoadClient(client.Id + 1);

    this.client.Id = newClient.Id;
    this.client.Name = newClient.Name;
}

But it is so tedious in a real project with many models and methods that can load/modify an object with a lot of fields. I'm looking for an easy/scalable way for making the PropertyChanged event fire when I assign a new object to the DataContext variable. If you need more detail of my code or have any question I'll be happy to ask.


Solution

  • The new instance of the object is created but has not been assigned to DataContext. Assignment to DataContext is required. Correct code is :

    private void btnNext_Click(object sender, RoutedEventArgs e)
        {
            client = Models.Clients.LoadClient(client.Id + 1);
            DataContext = client; 
        }