← Все вопросы

Привязка данных в WPF не обновляет UI — забыл INotifyPropertyChanged?

Задан 13 месяцев назад478 просмотров3 ответа
10

Сделал привязку {Binding UserName} к TextBlock, в коде меняю свойство UserName, но текст на экране не меняется. При первом запуске значение показывается правильно, а дальше — глухо. Где ошибка?

public class UserVm
{
    public string UserName { get; set; }
}
<TextBlock Text="{Binding UserName}"/>

3 ответа

17
✓ Принятый ответ — помог автору

Да, вы угадали — проблема в отсутствии INotifyPropertyChanged. Без него Binding читает значение один раз при загрузке и больше не знает, что свойство изменилось. Нужно, чтобы объект-источник «кричал» об изменениях через событие PropertyChanged.

Исправленная ViewModel:

using System.ComponentModel;
using System.Runtime.CompilerServices;

public class UserVm : INotifyPropertyChanged
{
    private string _userName = "";
    public string UserName
    {
        get => _userName;
        set
        {
            if (_userName == value) return;
            _userName = value;
            OnPropertyChanged();   // уведомляем UI
        }
    }

    public event PropertyChangedEventHandler? PropertyChanged;
    protected void OnPropertyChanged([CallerMemberName] string? name = null)
        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}

Ключевые моменты:

  1. Класс реализует INotifyPropertyChanged;
  2. В сеттере вызывается OnPropertyChanged();
  3. Атрибут [CallerMemberName] сам подставит имя свойства ("UserName"), не нужно писать строку руками — и нельзя ошибиться в названии.

XAML менять не нужно. Теперь любое присваивание vm.UserName = "..." мгновенно обновит TextBlock.

Частые грабли рядом:

  • если имя свойства в PropertyChangedEventArgs написать строкой и опечататься — UI молча не обновится;
  • для двусторонней привязки TextBox добавьте Mode=TwoWay (для Text он и так по умолчанию) и при необходимости UpdateSourceTrigger=PropertyChanged.
6

Чтобы не плодить однотипный код в каждом свойстве, посмотрите в сторону CommunityToolkit.Mvvm — там есть генератор исходного кода. Пишете так:

public partial class UserVm : ObservableObject
{
    [ObservableProperty]
    private string _userName = "";
}

И атрибут [ObservableProperty] сам сгенерирует публичное свойство UserName с вызовом уведомления. Меньше шаблонного кода, меньше шансов забыть OnPropertyChanged.

3

Ещё одна частая причина «не обновляется», даже когда INotifyPropertyChanged уже есть: вы заменяете весь объект-источник или не на ту коллекцию привязались. Если меняется список — обычный List<T> не уведомляет о добавлении/удалении элементов, нужен ObservableCollection<T>. Но в вашем конкретном случае с одним строковым свойством виновник именно отсутствие INotifyPropertyChanged.

Ваш ответ

Войдите, чтобы ответить на вопрос.
Поддержать проект