Ответ
Двусторонняя привязка (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
};
}
}
Как это работает:
- При запуске данные из ViewModel (
Name="Иван Иванов") автоматически отображаются вTextBoxиTextBlock. - Когда пользователь меняет текст в
TextBox, сеттер свойстваNameв ViewModel вызывается немедленно (благодаряUpdateSourceTrigger=PropertyChanged). - Вызов
OnPropertyChanged()в сеттере уведомляет WPF об изменении свойства. - 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
};
}
}
А теперь, как это всё ебётся, прости господи, работает:
- Окно открылось — в полях сразу красуется «Иван Иванов», 30 лет и галочка. Потому что данные из ViewModel автоматом в интерфейс перетекли.
- Ты начинаешь в поле имени печатать «Иван Дурак» — после каждой буквы сеттер свойства
Nameдергается (UpdateSourceTrigger=PropertyChanged). Он вызываетOnPropertyChanged()и кричит на всю форму: «Эй, народ, имя поменялось!». - WPF это слышит и бежит обновлять ВСЕ привязанные к этому свойству контролы. То есть и
TextBox, иTextBlockвнизу в предпросмотре. И всё само, блядь! Никакого ручногоtextBox1.Text = model.Name!
На что смотреть, чтобы не обосраться:
UpdateSourceTrigger: Для текстового поля часто ставятPropertyChanged, чтобы всё было сразу и живо. Но если там сложная логика на каждое изменение — может тормозить. Для всяких ползунков (Slider) или флажков обычно хватает значения по умолчанию.- Проверка введённого (валидация): Вся мощь TwoWay в том, что ошибки можно ловить прямо в сеттере свойства и красиво показывать. Через
INotifyDataErrorInfo,ValidationRulesили просто исключения (как у нас сValidatesOnExceptions=True). - Где DataContext брать: В нормальных больших приложениях его не в коде окна создают, а через всякие фреймворки (Prism, MVVM Toolkit) или контейнеры внедрения зависимостей подсовывают. Но суть одна — связать View и ViewModel.
Вот и вся магия. Ничего сложного, зато как удобно, ёбушки-воробушки!