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

Ответ

Двусторонняя привязка (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).

Ответ 18+ 🔞

Ну ты смотри, какая красота! Двусторонняя привязка в WPF — это ж как волшебство, только без всякой этой ерунды с волшебными палочками. Всё просто: изменил что-то в интерфейсе — данные в коде сами обновились. Изменил данные в коде — интерфейс сам подстроился. Красота же!

Вот смотри, как это на живом примере работает. Допустим, делаем мы форму для редактирования профиля какого-нибудь пользователя.

1. Сначала наша ViewModel (это типа мозги формы):

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();
            }
        }
    }

    // Это магия, которая всех оповещает, что свойство поменялось
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

2. Теперь разметка окна (XAML), то есть лицо нашей формы:

<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! Обновляется сразу, как только букву вписал -->
        <TextBox Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>

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

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

        <Separator Margin="0,10"/>

        <!-- А это просто для красоты, чтобы видеть, что там в данных сейчас -->
        <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. И главное — связать это всё в коде окна:

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

        // Подсовываем нашей форме свежеиспечённую ViewModel
        this.DataContext = new UserProfileViewModel
        {
            Name = "Иван Иванов",
            Age = 30,
            IsSubscribed = true
        };
    }
}

А теперь, как это всё ебётся, прости господи, работает:

  1. Окно открылось — в полях сразу красуется «Иван Иванов», 30 лет и галочка. Потому что данные из ViewModel автоматом в интерфейс перетекли.
  2. Ты начинаешь в поле имени печатать «Иван Дурак» — после каждой буквы сеттер свойства Name дергается (UpdateSourceTrigger=PropertyChanged). Он вызывает OnPropertyChanged() и кричит на всю форму: «Эй, народ, имя поменялось!».
  3. WPF это слышит и бежит обновлять ВСЕ привязанные к этому свойству контролы. То есть и TextBox, и TextBlock внизу в предпросмотре. И всё само, блядь! Никакого ручного textBox1.Text = model.Name!

На что смотреть, чтобы не обосраться:

  • UpdateSourceTrigger: Для текстового поля часто ставят PropertyChanged, чтобы всё было сразу и живо. Но если там сложная логика на каждое изменение — может тормозить. Для всяких ползунков (Slider) или флажков обычно хватает значения по умолчанию.
  • Проверка введённого (валидация): Вся мощь TwoWay в том, что ошибки можно ловить прямо в сеттере свойства и красиво показывать. Через INotifyDataErrorInfo, ValidationRules или просто исключения (как у нас с ValidatesOnExceptions=True).
  • Где DataContext брать: В нормальных больших приложениях его не в коде окна создают, а через всякие фреймворки (Prism, MVVM Toolkit) или контейнеры внедрения зависимостей подсовывают. Но суть одна — связать View и ViewModel.

Вот и вся магия. Ничего сложного, зато как удобно, ёбушки-воробушки!