For some particular reason I have declared a binding in C# code instead of declaring it in XAML as usual. The problem I am facing is that when I change only one property of the object, the INotifyPropertyChanged works properly and propagates the changes as expected, but when I change the entire object (the reference), it does not work as expected.
You can see it in this sample code, where I set one initial value of "111", which works well, then I change only one property value to "222" and also works as expected, but when I set a new object, then it does not work.
What should I change in the code so the thrid assignment also works and the Text property is set to "333" via binding?
public class MainWindowViewModel : INotifyPropertyChanged
{
private MySubClass _selectedItem;
public MySubClass SelectedItem
{
get => _selectedItem;
set
{
_selectedItem = value;
OnPropertyChanged(nameof(SelectedItem));
}
}
private MySubClass _selectedItemBefore;
public MySubClass SelectedItemBefore
{
get => _selectedItemBefore;
set
{
_selectedItemBefore = value;
OnPropertyChanged(nameof(SelectedItemBefore));
}
}
private MySubClass _selectedItemAfter;
public MySubClass SelectedItemAfter
{
get => _selectedItemAfter;
set
{
_selectedItemAfter = value;
OnPropertyChanged(nameof(SelectedItemAfter));
}
}
TextBox textBox = new TextBox();
public MainWindowViewModel()
{
SelectedItemBefore = new MySubClass
{
FirstProperty = "111"
};
SelectedItemAfter = new MySubClass
{
FirstProperty = "333"
};
SelectedItem = SelectedItemBefore;
var firstPropertyBinding = new Binding("FirstProperty") { Source = SelectedItem, Mode = BindingMode.TwoWay };
BindingOperations.SetBinding(textBox, TextBox.TextProperty, firstPropertyBinding);
Console.WriteLine($"Text here is: {textBox.Text}");
// It's "111", so OK!!!
SelectedItem.FirstProperty = "222";
Console.WriteLine($"Text here is: {textBox.Text}");
// It's "222", so OK!!!
SelectedItem = SelectedItemAfter;
Console.WriteLine($"Text here is: {textBox.Text}");
// ¡¡¡FAIL!!!: expected "333" but it is "222" yet
// (here SelectedItem.FirstProperty == "333")
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var changed = PropertyChanged;
if (changed == null)
return;
changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
public class MySubClass : INotifyPropertyChanged
{
private string _firstProperty;
public string FirstProperty
{
get => _firstProperty;
set
{
_firstProperty = value;
OnPropertyChanged(nameof(FirstProperty));
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var changed = PropertyChanged;
if (changed == null)
return;
changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
UPDATE: I know that a TextBox should never go in the ViewModel, nor anything related to the View, please, don't dwell on that detail. This code is just for learning purposes and to understand how Bindings work, but for now, I don't fully understand it. I would appreciate explanations on how to modify the code so that it does display "333".
First, let’s take a look at the data sources of
Binding:Source: Direct static binding source reference, which cannot be modified after binding is applied.{Reference name}to set a named element reference in this XAML scope.RelativeSource: According to the element to which the binding is applied and the mode of this RelativeSource, obtain other object as binding source based on the relative position of the binding applied element.RelativeSourcetarget changing will change the reference of this binding source.ElementName: Set the name of an usex:Namenamed element defined in current XAML and in same visual tree.Sourceand also static, but simpler.None of the above are set: Binding source is the object(Control) own DataContext property, DataContext can be inherited in visual tree or document flow.
DataContextchanging will change the reference of this binding source.Setted
Binding.Sourceproperty will set a static reference of object as a binding source on thisBinding.But looks you want
Bindingto theMySubClass.FirstPropertyfromMainWindowViewModel.SelectedItem, but you set theSelectedItemBeforestatic reference on this binding.So that you cannot do anyting with
SelectedItemAfter.The right way is that you should binding to
MySubClass.FirstPropertyof thisMainWindowViewModel.SelectedItem:If
SelectedItemchanged, it will notify this binding source is changed and binding engine will update this binging target value..FirstPropertymeans findFirstPropertyin theSelectedItemreferecd object.And if in the
WindoworUserControlcontext, it would be better to useDataContext.For example in window: