I found myself trying to explain to a colleague what the sequence of events is when you change a dependency property. Obviously it will be a bit different when you change a Dependency Property by changing the bound property vs changing it internally. I found I wasn't entirely sure what got called when.
So I created a simple WPF app that writes to Debug as control passed from the property to the dependency property and back. It explains the process quite clearly.
Start by creating a new WPF, C#, .Net Core application called BindingEvents in Visual Studio. Add a new class called AlphaTextBox. It subclasses TextBox and adds a DependencyProperty called IsAlphaOnly. We won't be implementing the functionality but we will be adding code to track what gets called and when.
Make the code of AlphaTextBox look like this. You can bind the IsAlphaOnly dependency property and you can also change it internally by double-clicking the text box.
internal class AlphaTextBox : TextBox
public static readonly DependencyProperty IsAlphaOnlyProperty = DependencyProperty.Register(nameof(IsAlphaOnly), typeof(bool), typeof(AlphaTextBox), new PropertyMetadata(false, IsAlphaOnly_Changed));
Debug.WriteLine("IsAlphaOnlyDependencyProperty_Changed");
get
{
Debug.WriteLine("IsAlphaOnlyDependencyProperty_Get");
set
{
Debug.WriteLine("IsAlphaOnlyDependencyProperty_Set_In");
Debug.WriteLine("IsAlphaOnlyDependencyProperty_Set_out");
}
this.MouseDoubleClick += AlphaTextBox_MouseDoubleClick;
Debug.WriteLine("AlphaTextBox_MouseDoubleClick_In");
}
}
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:BindingEvents"
mc:Ignorable="d"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<RoutedCommand x:Key="ToggleAlphaOnlyCommand"/>
</Window.Resources>
<Window.CommandBindings>
<CommandBinding Command="{StaticResource ToggleAlphaOnlyCommand}" Executed="ToggleAlphaOnly_Executed"/>
</Window.CommandBindings>
<StackPanel Orientation="Horizontal" Height="23">
<local:AlphaTextBox Width="100" Text="{Binding Text}" IsAlphaOnly="{Binding IsAlphaOnly}"/>
<Button Content="Toggle Alpha Only" Command="{StaticResource ToggleAlphaOnlyCommand}"/>
</StackPanel>
</Window>
namespace BindingEvents
public partial class MainWindow : Window, INotifyPropertyChanged
private String _Text;
get => _Text;
private bool _IsAlphaOnly;
get
{
Debug.WriteLine("IsAlphaOnly_Get");
set
{
Debug.WriteLine("IsAlphaOnly_Set_In");
}
public MainWindow()
Debug.WriteLine("----------------------------------------");
}
public event PropertyChangedEventHandler? PropertyChanged;
Debug.WriteLine("SetProperty");
{
storage = value;
if (PropertyChanged != null)
Debug.WriteLine("Raising PropertyChanged");
}
}
private void ToggleAlphaOnly_Executed(object sender, System.Windows.Input.ExecutedRoutedEventArgs e)
Debug.WriteLine("ToggleAlphaOnly_Executed_In");
Debug.WriteLine("ToggleAlphaOnly_Executed_Out");
}
}
If you run this application you see a text box and a button. You can toggle the IsAlphaOnly property by clicking on the button or by double-clicking in the text box.
ToggleAlphaOnly_Executed_In
IsAlphaOnly_Get
IsAlphaOnly_Set_In
SetProperty
Raising PropertyChanged
IsAlphaOnly_Get
IsAlphaOnlyDependencyProperty_Changed
Raised PropertyChanged
IsAlphaOnly_Set_Out
ToggleAlphaOnly_Executed_Out
IsAlphaOnlyDependencyProperty_Get
IsAlphaOnlyDependencyProperty_Set_In
IsAlphaOnlyDependencyProperty_Changed
IsAlphaOnlyDependencyProperty_Set_out
AlphaTextBox_MouseDoubleClick_Out
Here we see the DependencyProperty setter is called and, when it calls SetValue, the Changed event handler is called.. Any code that needs to be executed whenever the DependencyProperty is changed, regardless of whether the change was internal or external, needs to be in the Changed event handler. If you need to behave differently for internally and externally caused changes, you can use the DependencyProperty setter.
You will notice the SetProperty method does not raise PropertyChanged if the property does not appear to change. In ToggleAlphaOnly_Executed change the code to...
IsAlphaOnly = IsAlphaOnly;
Now run the application and click the button.
IsAlphaOnly_Get
IsAlphaOnly_Set_In
SetProperty
IsAlphaOnly_Set_Out
ToggleAlphaOnly_Executed_Out
You will notice PropertyChanged is not raised. This is a common pattern to use. Now let's modify SetProperty to always raise PropertyChanged.
//if (!Object.Equals(storage, value))
IsAlphaOnly_Get
IsAlphaOnly_Set_In
SetProperty
Raising PropertyChanged
IsAlphaOnly_Get
Raised PropertyChanged
IsAlphaOnly_Set_Out
ToggleAlphaOnly_Executed_Out