Data binding
DataContext, binding modes and update triggers, change notification, and converters that keep formatting out of the view model.
The binding pipeline
A binding connects a target property on a dependency object to a source property. The source is normally resolved against the element's DataContext, which is inherited down the visual tree — set it once on the window and every child can bind to it.
<Grid DataContext="{Binding}">
<TextBox Text="{Binding Customer.Name, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged,
ValidatesOnExceptions=True}" />
<!-- explicit source: another named element -->
<Slider x:Name="Amount" Minimum="0" Maximum="100" Value="20" />
<TextBlock Text="{Binding ElementName=Amount, Path=Value, StringFormat={}{0:F0} units}" />
<!-- explicit source: the view itself -->
<Button Content="Close"
Command="{Binding DataContext.CloseCommand, RelativeSource={RelativeSource AncestorType=Window}}" />
<!-- fallback when the binding cannot resolve -->
<TextBlock Text="{Binding Missing.Path, TargetNullValue='(not set)',
FallbackValue='-'}" />
</Grid>| Setting | Values | Effect |
|---|---|---|
Mode | OneWay | Source to target only; the default for most targets |
Mode | TwoWay | Both directions; needed for input controls |
Mode | OneTime | Reads once and never updates |
UpdateSourceTrigger | PropertyChanged | Writes on every keystroke — validation as you type |
UpdateSourceTrigger | LostFocus | The default for TextBox.Text |
UpdateSourceTrigger | Explicit | Only when you call UpdateSource yourself |
NotifyOnSourceUpdated | true | Raises an event so you can react to incoming changes |
- A silent binding failure is the norm. Turn up tracing with the
PresentationTraceSources.TraceLevelattached property on the binding to see what the framework tried. FallbackValuecovers a binding that cannot resolve;TargetNullValuecovers a source that resolves tonull. They are different problems.RelativeSource AncestorTypeis how a control inside anItemTemplatereaches the window-level view model, because inside a template theDataContextis the item.
Change notification
For a target to update when the source changes, the source must tell WPF. That means INotifyPropertyChanged on the item and ObservableCollection<T> for lists.
public sealed class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
protected bool Set<T>(ref T field, T value, [CallerMemberName] string? name = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
return true;
}
}
public sealed class OrderViewModel : ViewModelBase
{
private string _customer = string.Empty;
private decimal _total;
public string Customer
{
get => _customer;
set
{
if (Set(ref _customer, value)) OnPropertyChanged(nameof(DisplayName));
}
}
public decimal Total
{
get => _total;
set { if (Set(ref _total, value)) OnPropertyChanged(nameof(DisplayName)); }
}
public string DisplayName => $"{Customer} - {Total:C}";
private void OnPropertyChanged(string name) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
public ObservableCollection<OrderLine> Lines { get; } = new();
}- Raise
PropertyChangedfor computed properties too, or the UI keeps showing a stale derived value. ObservableCollection<T>notifies on add, remove and reset. Replacing an element notifies nothing — assign a new item or raise the event manually.- Mutating an item inside the collection does not notify the collection; the item itself must implement
INotifyPropertyChanged. - Bind a
CollectionViewSourcewhen you need sorting, filtering or grouping without changing the underlying collection.
Converters and formatting
public sealed class BoolToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) =>
value is true ? Visibility.Visible : Visibility.Collapsed;
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
value is Visibility.Visible;
}
public sealed class MoneyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) =>
((decimal)value).ToString("C0", culture);
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
decimal.Parse((string)value, NumberStyles.Currency, culture);
}<Window.Resources>
<local:BoolToVisibilityConverter x:Key="BoolToVis" />
<local:MoneyConverter x:Key="Money" />
<BooleanToVisibilityConverter x:Key="BuiltInBoolToVis" />
</Window.Resources>
<TextBlock Text="{Binding Total, Converter={StaticResource Money}}" />
<Border Visibility="{Binding HasError, Converter={StaticResource BoolToVis}}" />⚠️
A converter that throws returns a silent binding failure and the control renders its default. Validate the input type and culture in
Convert, and prefer a typed converter over one that quietly swallows every exception.FAQ
Nothing is showing up in my bound control. Where do I look?
Check the output window for binding errors, confirm the
DataContext is actually set on this or a parent element, and verify the property name matches exactly — bindings are case-sensitive and fail silently by default.Is StringFormat or a converter the better choice?
Use
StringFormat for a simple format string and a converter when the transformation is genuinely not a formatting concern — mapping a boolean to visibility, or combining several properties into one value.Related
XAML and layout panels MVVM basics and commands
Last refreshed 2026-09-18.