Почему обновление привязки не реализует INotifyPropertyChanged?

Я создал ViewModel и связал его свойство с двумя текстовыми полями в пользовательском интерфейсе. Значение другого текстового поля изменяется при изменении значения первого и фокуса из текстового поля, но я не внедряю INotifyPropertyChanged. Как это работает?

Ниже приведен XAML

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication1"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <local:ViewModel />
    </Window.DataContext>
    <StackPanel>
        <TextBox Text="{Binding Name}" />
        <TextBox Text="{Binding Name}" />
    </StackPanel>
</Window>

И вот мой ViewModel

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

Ответ 1

Я тестировал его, вы правы. Теперь я искал его в Интернете и нашел this.

Извините, что так долго отвечал, на самом деле вы сталкиваетесь с еще одним скрытым аспектом WPF, что его механизм привязки данных WPF привяжет данные к экземпляру PropertyDescriptor, который обертывает свойство source, если исходный объект является простым объектом CLR и не реализует интерфейс INotifyPropertyChanged. И механизм привязки данных попытается подписаться на событие с измененным свойством через метод PropertyDescriptor.AddValueChanged(). И когда элемент данных, привязанный к целевым данным, изменит значения свойств, механизм привязки данных вызовет метод PropertyDescriptor.SetValue() для переноса измененного значения обратно в исходное свойство и одновременно будет вызывать событие ValueChanged для уведомления других подписчиков (в данном случае, другие подписчики будут TextBlocks в ListBox.

И если вы внедряете INotifyPropertyChanged, вы несете полную ответственность за внедрение уведомления об изменении в каждом наборе свойств, которые должны быть привязаны к пользовательскому интерфейсу. В противном случае изменение не будет синхронизировано, как вы ожидали.

Надеюсь, что это немного облегчит ситуацию.

Таким образом, вы можете сделать это, если это простой объект CLR. Довольно аккуратный, но совершенно неожиданный - и я проделал немного работы WPF в последние годы. Вы никогда не перестаете изучать новые вещи, верно?

Как предложил Хасан Хан, еще одна ссылка на довольно интересную статью по этому вопросу.

Примечание это работает только при использовании привязки. Если вы обновите значения из кода, изменение не будет уведомлено. [...]

WPF использует класс PropertyInfo с гораздо меньшим весом при привязке. Если вы явно реализуете INotifyPropertyChanged, все, что нужно WPF, это вызвать метод PropertyInfo.GetValue, чтобы получить последнее значение. Это немного меньше, чем получение всех дескрипторов. Дескрипторы заканчиваются калькуляцией порядка 4x памяти классов информации о свойствах. [...]

Реализация INotifyPropertyChanged может быть справедливой частью утомительной разработки. Тем не менее, вам нужно взвесить эту работу против рабочего пространства (памяти и процессора) вашего приложения WPF. Внедрение INPC само по себе позволит сэкономить процессор и память времени выполнения.

Ответ 2

Я могу объяснить, почему свойство обновляется при изменении фокуса: все Binding имеют свойство UpdateSourceTrigger, которое указывает, когда свойство source будет обновлено. Значение по умолчанию для этого определено для каждого DependencyProperty, а для свойства TextBox.Text установлено значение LostFocus, что означает, что свойство будет обновляться, когда элемент управления теряет фокус.

Я считаю, что ответ UrbanEsc объясняет, почему значение обновляется вообще

Ответ 3

Я только узнал, что это также работает в WinForms, вроде:/

public class Test
{
    public bool IsEnabled { get; set; }
}

var test = new Test();
var btn = new Button { Enabled = false, Text = "Button" };
var binding = new Binding("Enabled", test, "IsEnabled");
btn.DataBindings.Add(binding);
var frm = new Form();
frm.Controls.Add(btn);
test.IsEnabled = true;
Application.Run(frm);

Странно, но это не отключает кнопку:

btn.Enabled = false;

Это делает:

test.IsEnabled = false;