There are several "WPF Databinding Not Updating" questions on here, and I've read through them all, including the top-ranked one: WPF DataBinding not updating?
However, I'm swapping out entire objects in my situation, and what's worse is that when I try to create a reproducible version of what I'm doing, it works in that version:
<Window x:Class="WpfApp2.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:local="clr-namespace:WpfApp2"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<Label Content="Contact Address" />
<TextBox x:Name="txtContactAddress" Text="{Binding Path=Contact.Address, Mode=OneWay}" />
<Label Content="Organization Address" />
<TextBox x:Name="txtOrgAddress" Text="{Binding Path=Organization.Address, Mode=OneWay}" />
<Button Content="Next Contact" Click="Button_Click" />
</StackPanel>
</Window>
Code-Behind:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApp2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public RecordWrapper CurrentRecord;
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
CurrentRecord = new RecordWrapper(new ContactData());
this.DataContext = CurrentRecord;
}
}
public class RecordWrapper : INotifyPropertyChanged
{
private ContactData _Contact;
public ContactData Contact
{
get { return _Contact; }
set
{
_Contact = value;
OnPropertyChanged("Contact");
Organization = _Contact.Organization;
}
}
private OrganizationData _Organization;
public OrganizationData Organization
{
get { return _Organization; }
set
{
_Organization = value;
OnPropertyChanged("Organization");
}
}
public RecordWrapper(ContactData contactData)
{
Contact = contactData;
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
#endregion
}
public class ContactData
{
public string Address { get; set; }
public OrganizationData Organization { get; set; }
public ContactData()
{
Address = new Random().Next(1000, 9999) + " Contact Street";
Organization = new OrganizationData();
}
}
public class OrganizationData
{
public string Address { get; set; }
public OrganizationData()
{
Address = new Random().Next(1000, 9999) + " Org Street";
}
}
}
So this particular sample works as expected - when I click on the "Next Contact" button, it generates a new RecordWrapper object that contains a new ContactData and new OrganizationData object, and then assigns the new object to DataContext and then I see the textboxes update properly.
Since I'm replacing the entire object in the DataContext, I can even ditch the property notifications and reduce the RecordWrapper down to just:
public class RecordWrapper
{
public ContactData Contact { get; set; }
public OrganizationData Organization { get; set; }
public RecordWrapper(ContactData contactData)
{
Contact = contactData;
Organization = Contact.Organization;
}
}
...and it still works great.
My real code, however, reflects the first example.
In the real code, it seems like updating this.DataContext with a new object doesn't work. Nothing is updated, but there are no errors, either. Setting the path in XAML doesn't work, either. In fact, the only way I can get it to work is to use code-behind to set the bindings directly to the data object:
BindingOperations.SetBinding(txtContactAddress, TextBox.TextProperty, new Binding() { Source = this.RecordWrapper, Path = new PropertyPath("Contact.Address"), Mode = BindingMode.OneWay });
When I do this, I can see it working, but that's obviously missing the point of binding if I have to run code on each field each time to update the bindings.
I'm stumped because the real code is nearly identical to the example aside from having more fields, different field names, and differences that have nothing to do with data or binding. When I click on a button, this is the exact code:
this.Data = new DataBindings(CurrentContact.FetchFake());
this.DataContext = this.Data;
The FetchFake() method generates a new CurrentContact object with some fake data, and I have verified that after that first line runs, this.Data.CurrentContact.Login is populated and is a read-only property of the CurrentContact object (not a field).
...and one of the XAML textboxes looks like this:
<TextBox x:Name="txtAccountNumber" Grid.Column="1" Grid.Row="1" Width="Auto" Text="{Binding Path=CurrentContact.Login, Mode=OneWay}"/>
The result is blank. I have also tried variations on order (e.g. setting DataContext to this.Data first, then setting the this.Data.CurrentContact property and letting the property-changed notification kick off, but still no luck. But if I run:
BindingOperations.SetBinding(tabAccount.txtAccountNumber, TextBox.TextProperty, new Binding() { Source = this.Data, Path = new PropertyPath("CurrentContact.Login"), Mode = BindingMode.OneWay });
...then I'll see the value show up.
Based on these symptoms, and the fact that my first example works but the real code doesn't, can anyone think of any place I should look for some culprits?