Control which way data flows with OneWay, TwoWay, OneTime and OneWayToSource, choose when typed text is saved with UpdateSourceTrigger, and build an edit form with Save and Cancel.

Bishal Biswas
Self Learner
Which way, and when?
In Data Binding Basics you saw that a TextBox bound to Title both shows the title and writes typed text back. A TextBlock bound to the same property only shows it. Why the difference?
Every binding answers two questions:
- Which way does data flow? From the source to the control, from the control to the source, or both? This is the binding mode.
- When does typed text go back to the source? On every key press, when the user leaves the box, or only when you say so? This is the update source trigger.
Getting these right makes forms feel natural. Getting them wrong gives you values that “do not save” or labels that never change.
The four binding modes
You set the mode with Mode= inside the binding:
<TextBox Text="{Binding Title, Mode=TwoWay}" />
Here is what each mode does. “Source” is your C# object and “target” is the control property.
| Mode | Direction | Typical use |
|---|---|---|
OneWay |
Source to target | Labels, totals, anything read only |
TwoWay |
Both ways | Text boxes, check boxes, sliders, date pickers |
OneTime |
Source to target, once at the start | Values that never change, such as an app version |
OneWayToSource |
Target to source | Rare. Pushes a control value into your object without reading it back |
OneWay source ------> target
TwoWay source <-----> target
OneTime source --1x--> target
OneWayToSource source <------ target
Default: each property picks its own
If you leave Mode out, you get Default, which means “whatever this control property prefers”. The designers of WPF chose sensible defaults:
- TwoWay by default:
TextBox.Text,CheckBox.IsChecked,Slider.Value,DatePicker.SelectedDate,ComboBox.SelectedItemand other properties the user edits. - OneWay by default: almost everything else, such as
TextBlock.Text,Content,Visibility,Foreground.
So most of the time you can skip Mode completely. Write it when you want something different, or when you want to make your intent clear to the next reader.
OneWay on an input control
Sometimes you want a TextBox that shows a value but never changes it, for example a read only “Id” box that the user can still select and copy:
<TextBox Text="{Binding Id, Mode=OneWay}" IsReadOnly="True" />
IsReadOnly stops typing, and Mode=OneWay makes it clear that nothing should flow back.
OneTime for values that never change
OneTime reads the value once when the binding starts and then stops listening. It is slightly cheaper than OneWay, which matters in long lists:
<TextBlock Text="{Binding Id, Mode=OneTime, StringFormat='Expense #{0}'}" />
Only use it when you are sure the value will not change while the screen is open.
Seeing modes in action
Bindings between two controls are perfect for experiments, because WPF controls always tell the binding when their properties change. Paste this into a window and play:
<StackPanel Margin="16">
<TextBox x:Name="Original" Text="Coffee" />
<!-- Copies follow the original. Type into them to see what flows back. -->
<TextBox Text="{Binding Text, ElementName=Original, Mode=OneWay}" Margin="0,8,0,0" />
<TextBox Text="{Binding Text, ElementName=Original, Mode=TwoWay}" Margin="0,8,0,0" />
<TextBox Text="{Binding Text, ElementName=Original, Mode=OneTime}" Margin="0,8,0,0" />
</StackPanel>
Try it:
- Type in the top box. The
OneWayandTwoWaycopies follow along. TheOneTimecopy stays as “Coffee”. - Type in the
TwoWaycopy and press Tab. The original changes too. - Type in the
OneWaycopy. The original does not change. Then type in the original again: theOneWaycopy jumps back in line, throwing away what you typed.
Notice that in the second step the original only changed when you pressed Tab, not while you were typing. That is the update source trigger at work.
UpdateSourceTrigger: when values go back
When the binding sends a value from the control back to the source, UpdateSourceTrigger decides the moment:
| Value | When the source is updated |
|---|---|
PropertyChanged |
Every time the control’s value changes, for example each key press |
LostFocus |
When the user leaves the control |
Explicit |
Only when your code asks |
Default |
Whatever the property prefers |
For most properties the default is PropertyChanged. The big exception is TextBox.Text, which defaults to LostFocus. Writing back on every key press could be slow or annoying, for example validating a half typed email address.
Saving as you type
For a search box or a live preview you want every key press. Say so:
<TextBox Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}" />
This one setting is probably the binding option you will type most often in WPF.
Waiting a moment with Delay
If each change triggers real work, like filtering a long list, add Delay. The binding then waits until the user stops typing for that many milliseconds:
<TextBox Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged, Delay=400}" />
Type “pizza” quickly and the source gets “pizza” once, not “p”, “pi”, “piz” and so on.
Explicit: only when the user clicks Save
Explicit means “do not write anything back until I say so”. This is ideal for a classic edit form with Save and Cancel buttons. The user can type freely, and the real object only changes on Save.
<TextBox x:Name="TitleBox"
Text="{Binding Title, UpdateSourceTrigger=Explicit}" />
<TextBox x:Name="AmountBox"
Text="{Binding Amount, UpdateSourceTrigger=Explicit}" />
In code, each binding is represented by a binding expression, the live object WPF creates for that binding on that control. You ask it to push or pull the value:
using System.Windows.Controls;
private void Save_Click(object sender, RoutedEventArgs e)
{
// Copy what the user typed into the Expense object
TitleBox.GetBindingExpression(TextBox.TextProperty)?.UpdateSource();
AmountBox.GetBindingExpression(TextBox.TextProperty)?.UpdateSource();
}
private void Cancel_Click(object sender, RoutedEventArgs e)
{
// Throw away the typing and show the object's values again
TitleBox.GetBindingExpression(TextBox.TextProperty)?.UpdateTarget();
AmountBox.GetBindingExpression(TextBox.TextProperty)?.UpdateTarget();
}
UpdateSource() sends the control value to the object. UpdateTarget() does the opposite and reloads the control from the object. The ?. is there because GetBindingExpression returns null if the property has no binding.
Handy extras: FallbackValue and TargetNullValue
Two more binding settings save you from blank or ugly screens:
<!-- Shown when the value itself is null -->
<TextBlock Text="{Binding Notes, TargetNullValue='(no notes)'}" />
<!-- Shown when the binding cannot get a value at all, for example no DataContext yet -->
<TextBlock Text="{Binding Title, FallbackValue='Loading...'}" />
TargetNullValueis for “the data is there but empty”.FallbackValueis for “the binding is broken or has nothing to read yet”.
Common mistakes
- “My text does not save.” Usually it does, just later than you expect.
TextBox.Textsaves onLostFocus. If the user clicks a toolbar button, the box may not lose focus first. UseUpdateSourceTrigger=PropertyChangedwhen that matters. - TwoWay binding to a read only property. If
Totalhas only aget, then{Binding Total}on aTextBoxthrows an error at runtime, because aTextBoxwants to write back. AddMode=OneWay. - Using
OneTimefor something that changes. It will look fine on first load and then never update. - Writing
Mode=TwoWayeverywhere “just in case”. It adds noise, and on aTextBlockit does nothing useful. Trust the defaults. - Expecting
OneWayto refresh from a plain object. AOneWaybinding only refreshes when the source announces a change. Controls always announce. Your own classes must implementINotifyPropertyChanged, which is the next lesson.
Tips
- Use
PropertyChangedfor search boxes, live previews and anything with instant feedback. - Keep the
LostFocusdefault for normal form fields. - Use
Explicit(or a copy of the object) when you need a real Cancel button. - Later, in the MVVM lessons, you will usually bind straight to a view model and use commands for Save. The ideas from this lesson stay exactly the same. See Introduction to MVVM.
Quick recap
Modecontrols direction:OneWay,TwoWay,OneTimeandOneWayToSource. Leave it out to get each property’s sensible default.- Input properties such as
TextBox.TextandCheckBox.IsCheckedareTwoWayby default. Display properties areOneWay. UpdateSourceTriggercontrols when values go back:PropertyChanged,LostFocusorExplicit.TextBox.Textdefaults toLostFocus. AddUpdateSourceTrigger=PropertyChangedfor live updates, plusDelayfor slow work.GetBindingExpression(...).UpdateSource()andUpdateTarget()give you Save and Cancel.TargetNullValueandFallbackValuefill gaps when the value is null or missing.- Next, make your own classes announce changes in INotifyPropertyChanged.
<!-- MainWindow.xaml -->
<Window x:Class="CoinJar.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="CoinJar: edit with Save and Cancel" Width="420" Height="420">
<!-- DataContext is an Expense, set in MainWindow.xaml.cs -->
<StackPanel Margin="16">
<!-- OneTime: the Id never changes while this form is open -->
<TextBlock Text="{Binding Id, Mode=OneTime, StringFormat='Editing expense #{0}'}"
FontWeight="SemiBold" />
<!-- Explicit: nothing is written until Save is clicked -->
<TextBlock Text="Title" Margin="0,12,0,0" />
<TextBox x:Name="TitleBox" Text="{Binding Title, UpdateSourceTrigger=Explicit}" />
<TextBlock Text="Amount" Margin="0,8,0,0" />
<TextBox x:Name="AmountBox" Text="{Binding Amount, UpdateSourceTrigger=Explicit}" />
<TextBlock Text="Category" Margin="0,8,0,0" />
<!-- The items (plain strings) are set in code-behind, so SelectedItem is a string like Category -->
<ComboBox x:Name="CategoryBox" SelectedItem="{Binding Category, UpdateSourceTrigger=Explicit}" />
<!-- Bound to the TextBox itself, so it follows every key press even before Save -->
<TextBlock Text="Live preview of the title box" Margin="0,12,0,0" Foreground="Gray" />
<TextBlock Text="{Binding Text, ElementName=TitleBox, Mode=OneWay}" FontSize="16" />
<StackPanel Orientation="Horizontal" Margin="0,16,0,0">
<Button Content="Save" Click="Save_Click" Padding="16,4" IsDefault="True" />
<Button Content="Cancel" Click="Cancel_Click" Padding="16,4" Margin="8,0,0,0" IsCancel="True" />
</StackPanel>
<TextBlock x:Name="SavedText" Margin="0,12,0,0" Foreground="Gray" TextWrapping="Wrap" />
</StackPanel>
</Window>
// MainWindow.xaml.cs
using System.Windows;
using System.Windows.Controls;
using CoinJar.Models;
namespace CoinJar;
public partial class MainWindow : Window
{
private readonly Expense _expense = new()
{
Id = 7,
Title = "Bus ticket",
Amount = 2.40m,
Category = "Transport"
};
public MainWindow()
{
InitializeComponent();
// Fill the ComboBox with the five CoinJar categories
CategoryBox.ItemsSource = new[] { "Food", "Transport", "Bills", "Fun", "Other" };
DataContext = _expense;
ShowSaved();
}
private void Save_Click(object sender, RoutedEventArgs e)
{
// Push each control value into the Expense object
TitleBox.GetBindingExpression(TextBox.TextProperty)?.UpdateSource();
AmountBox.GetBindingExpression(TextBox.TextProperty)?.UpdateSource();
CategoryBox.GetBindingExpression(ComboBox.SelectedItemProperty)?.UpdateSource();
ShowSaved();
}
private void Cancel_Click(object sender, RoutedEventArgs e)
{
// Reload each control from the Expense object, dropping the edits
TitleBox.GetBindingExpression(TextBox.TextProperty)?.UpdateTarget();
AmountBox.GetBindingExpression(TextBox.TextProperty)?.UpdateTarget();
CategoryBox.GetBindingExpression(ComboBox.SelectedItemProperty)?.UpdateTarget();
}
private void ShowSaved()
{
SavedText.Text = $"Saved object: {_expense.Title}, {_expense.Amount:C}, {_expense.Category}";
}
}
// Models/Expense.cs
namespace CoinJar.Models;
public class Expense
{
public int Id { get; set; }
public string Title { get; set; } = "";
public decimal Amount { get; set; }
public string Category { get; set; } = "Other";
public DateTime Date { get; set; } = DateTime.Today;
}
Was this explanation helpful?
Your feedback helps us improve the content for everyone.
Thank you for your feedback!
We appreciate you helping us improve.
What could we improve? (Optional)
Read aloud
Ready