Здесь есть несколько вопросов «WPF Databinding Not Updating», и я прочитал их все, включая один из самых популярных:
Связывание данных WPF не обновляется?
Тем не менее, я заменяю целые объекты в моей ситуации, и что еще хуже, когда я пытаюсь создать воспроизводимую версию того, что я делаю, она работает в этой версии:
<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";
}
}
}
Так что этот конкретный пример работает, как и ожидалось - когда я нажимаю кнопку «Следующий контакт», он генерирует новый объект RecordWrapper, который содержит новые ContactData и новый объект OrganizationData, а затем назначает новый объект DataContext, а затем я вижу текстовые поля обновляются правильно.
Поскольку я заменяю весь объект в DataContext, я могу даже отказаться от уведомлений о свойствах и уменьшить RecordWrapper до всего:
public class RecordWrapper
{
public ContactData Contact { get; set; }
public OrganizationData Organization { get; set; }
public RecordWrapper(ContactData contactData)
{
Contact = contactData;
Organization = Contact.Organization;
}
}
... и все равно прекрасно работает.
Однако мой реальный код отражает первый пример.
В реальном коде кажется, что обновление this.DataContext новым объектом не работает. Ничего не обновляется, но ошибок тоже нет. Установка пути в XAML тоже не работает. Фактически, единственный способ заставить его работать - это использовать программный код для установки привязок непосредственно к объекту данных:
BindingOperations.SetBinding(txtContactAddress, TextBox.TextProperty, new Binding() { Source = this.RecordWrapper, Path = new PropertyPath("Contact.Address"), Mode = BindingMode.OneWay });
Когда я это делаю, я вижу, как это работает, но явно не хватает точки привязки, если мне приходится каждый раз запускать код для каждого поля для обновления привязок.
Я в замешательстве, потому что реальный код почти идентичен примеру, за исключением того, что он содержит больше полей, разных имен полей и различий, которые не имеют ничего общего с данными или связыванием. Когда я нажимаю на кнопку, это точный код:
this.Data = new DataBindings(CurrentContact.FetchFake());
this.DataContext = this.Data;
Метод FetchFake () генерирует новый объект CurrentContact с некоторыми поддельными данными, и я убедился, что после запуска первой строки this.Data.CurrentContact.Login заполняется и является доступным только для чтения свойством объекта CurrentContact ( не поле).
... и одно из текстовых полей XAML выглядит так:
<TextBox x:Name="txtAccountNumber" Grid.Column="1" Grid.Row="1" Width="Auto" Text="{Binding Path=CurrentContact.Login, Mode=OneWay}"/>
Результат пуст. Я также пробовал варианты порядка (например, сначала установите DataContext на this.Data, затем установите свойство this.Data.CurrentContact и разрешите уведомление об изменении свойства, но все равно не повезло. Но если я запусту:
BindingOperations.SetBinding(tabAccount.txtAccountNumber, TextBox.TextProperty, new Binding() { Source = this.Data, Path = new PropertyPath("CurrentContact.Login"), Mode = BindingMode.OneWay });
... тогда я увижу значение.
Исходя из этих симптомов и того факта, что мой первый пример работает, а реальный код не работает, кто-нибудь может подумать о каком-либо месте, где мне следует искать виновных?