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>
SettingValuesEffect
ModeOneWaySource to target only; the default for most targets
ModeTwoWayBoth directions; needed for input controls
ModeOneTimeReads once and never updates
UpdateSourceTriggerPropertyChangedWrites on every keystroke — validation as you type
UpdateSourceTriggerLostFocusThe default for TextBox.Text
UpdateSourceTriggerExplicitOnly when you call UpdateSource yourself
NotifyOnSourceUpdatedtrueRaises an event so you can react to incoming changes
  • A silent binding failure is the norm. Turn up tracing with the PresentationTraceSources.TraceLevel attached property on the binding to see what the framework tried.
  • FallbackValue covers a binding that cannot resolve; TargetNullValue covers a source that resolves to null. They are different problems.
  • RelativeSource AncestorType is how a control inside an ItemTemplate reaches the window-level view model, because inside a template the DataContext is 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 PropertyChanged for 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 CollectionViewSource when 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.

XAML and layout panels MVVM basics and commands

Last refreshed 2026-09-18.