Приведи пример использования двусторонней привязки данных (TwoWay Binding) в WPF

«Приведи пример использования двусторонней привязки данных (TwoWay Binding) в WPF» — вопрос из категории C# Core, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Двусторонняя привязка (TwoWay) — это основа интерактивных форм в WPF. Она обеспечивает автоматическую синхронизацию данных между свойством в UI-элементе (цель) и свойством в источнике данных (чаще всего ViewModel).

Полный рабочий пример: Форма редактирования профиля.

1. ViewModel (реализует INotifyPropertyChanged):

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

public class UserProfileViewModel : INotifyPropertyChanged
{
    private string _name;
    private int _age;
    private bool _isSubscribed;

    public string Name
    {
        get => _name;
        set
        {
            if (_name != value)
            {
                _name = value;
                OnPropertyChanged();
                // Можно добавить логику, например, валидацию:
                // if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException("Имя не может быть пустым");
            }
        }
    }

    public int Age
    {
        get => _age;
        set
        {
            if (_age != value && value >= 0)
            {
                _age = value;
                OnPropertyChanged();
            }
        }
    }

    public bool IsSubscribed
    {
        get => _isSubscribed;
        set
        {
            if (_isSubscribed != value)
            {
                _isSubscribed = value;
                OnPropertyChanged();
            }
        }
    }

    // Реализация INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

2. XAML-разметка (View):

<Window x:Class="ProfileEditor.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Редактор профиля" Height="250" Width="300">
    <StackPanel Margin="10">
        <Label>Имя:</Label>
        <!-- TwoWay binding с обновлением источника при каждом изменении текста -->
        <TextBox Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>

        <Label>Возраст:</Label>
        <!-- TwoWay binding для числового поля. ValidatesOnExceptions=True перехватывает исключения из сеттера -->
        <TextBox Text="{Binding Age, Mode=TwoWay, UpdateSourceTrigger=LostFocus, ValidatesOnExceptions=True}"/>

        <Label>Подписка на новости:</Label>
        <!-- Для CheckBox IsChecked по умолчанию уже TwoWay -->
        <CheckBox IsChecked="{Binding IsSubscribed}" Content="Получать уведомления"/>

        <Separator Margin="0,10"/>

        <!-- OneWay binding для отображения текущих данных из ViewModel -->
        <TextBlock FontWeight="Bold" Text="Предпросмотр:"/>
        <TextBlock Text="{Binding Name, StringFormat='Имя: {0}'}"/>
        <TextBlock Text="{Binding Age, StringFormat='Возраст: {0}'}"/>
        <TextBlock Text="{Binding IsSubscribed, StringFormat='Подписка активна: {0}'}"/>
    </StackPanel>
</Window>

3. Установка DataContext (например, в коде окна):

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        // Создаем и присваиваем ViewModel как контекст данных для всего окна
        this.DataContext = new UserProfileViewModel
        {
            Name = "Иван Иванов",
            Age = 30,
            IsSubscribed = true
        };
    }
}

Как это работает:

  1. При запуске данные из ViewModel (Name="Иван Иванов") автоматически отображаются в TextBox и TextBlock.
  2. Когда пользователь меняет текст в TextBox, сеттер свойства Name в ViewModel вызывается немедленно (благодаря UpdateSourceTrigger=PropertyChanged).
  3. Вызов OnPropertyChanged() в сеттере уведомляет WPF об изменении свойства.
  4. WPF автоматически обновляет все элементы, привязанные к этому свойству, включая TextBlock в секции «Предпросмотр».

Важные нюансы:

  • UpdateSourceTrigger: Для TextBox часто используют PropertyChanged для мгновенной обратной связи, но это может быть накладно для тяжелых операций. Для Slider или CheckBox обычно оставляют значение по умолчанию.
  • Валидация: Двусторонняя привязка позволяет легко интегрировать валидацию через INotifyDataErrorInfo, ValidationRules или исключения в сеттерах (ValidatesOnExceptions).
  • Источник данных: В реальных приложениях DataContext обычно устанавливается через DI-контейнер (например, в MVVM-фреймворках типа Prism или MVVM Toolkit).