I know what you think: It is 2017, please don't come up with this again, but I really can not find any valueable explanation for this.
Please have a look at the ActiveNotes property in this XAML-Code.
I have this TwoWay binding in my XAML, which works perfectly. It is ALWAYS updated, if the PropertyChanged event for ScaleNotes is fired and if the binding is set to TwoWay.
<c:Keyboard
Grid.Row="2"
Grid.Column="0"
PlayCommand="{Binding PlayCommand}"
StopCommand="{Binding StopCommand}"
ActiveNotes="{Binding ScaleNotes, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
The ScaleNotes property in the ViewModel looks like this. Whenever it changes, the PropertyChanged event is guaranteed to be fired. I checked and double checked it. The business logic in the ViewModel works.
private ReadOnlyCollection<eNote> _ScaleNotes;
public ReadOnlyCollection<eNote> ScaleNotes
{
get { return _ScaleNotes; }
set { SetField(ref _ScaleNotes, value); }
}
[DebuggerStepThrough]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
[DebuggerStepThrough]
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
Up to here everything is ok. Whenever the ScaleNotes property in the VM is changed, the target property ActiveNotes is updated.
Now the problem:
If I only change the binding in the XAML to OneWay and the business logic in the VM stays 100% the same, the ActivesNotes property in the target object is updated only once even if the PropertyChanged event is fired. I checked and double checked it. The PropertyChanged event for the ScaleNotes property is always fired.
<c:Keyboard
Grid.Row="2"
Grid.Column="0"
PlayCommand="{Binding PlayCommand}"
StopCommand="{Binding StopCommand}"
ActiveNotes="{Binding ScaleNotes, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
Just to make this complete, here is DP in the target object.
public static DependencyProperty ActiveNotesProperty = DependencyProperty.Register(
"ActiveNotes",
typeof(ReadOnlyCollection<eNote>),
typeof(Keyboard),
new PropertyMetadata(OnActiveNotesChanged));
public ReadOnlyCollection<eNote> ActiveNotes
{
get
{
return (ReadOnlyCollection<eNote>)GetValue(ActiveNotesProperty);
}
set
{
SetValue(ActiveNotesProperty, value);
}
}
private static void OnActiveNotesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Keyboard keyboard = (Keyboard)d;
keyboard.ActiveNotes = (ReadOnlyCollection<eNote>)e.NewValue;
if ((keyboard.ActiveNotes != null) && (keyboard.ActiveNotes.Count > 0))
{
keyboard.AllKeys.ForEach(k => { if ( k.Note != eNote.Undefined) k.IsActiveKey = true; });
keyboard.AllKeys.ForEach(k => { if ((k.Note != eNote.Undefined) && (!keyboard.ActiveNotes.Contains(k.Note))) k.IsActiveKey = false; });
}
else
{
keyboard.AllKeys.ForEach(k => { if (k.Note != eNote.Undefined) k.IsActiveKey = true; });
}
}
I don't understand this. From my knowledge, OneWay and TwoWay only define in which direction the values are updated and not how often they can be updated.
I can not understand, that everything works fine with TwoWay, the business logic stays 100% the same and OneWay is a deal breaker.
If you ask yourself, why I want to know this: This binding was planned as a OneWay binding. It makes no sense to update the source in any way. I only changed it to TwoWay, because OneWay doesn't work as expected.
SOLUTION with the help of @MikeStrobel: (see comments)
The code needs be changed this way:
private static void OnActiveNotesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Keyboard keyboard = (Keyboard)d;
//THIS LINE BREAKED THE CODE, WHEN USING OneWay binding BUT NOT WITH TwoWay binding
//keyboard.ActiveNotes = (ReadOnlyCollection<eNote>)e.NewValue;
if ((keyboard.ActiveNotes != null) && (keyboard.ActiveNotes.Count > 0))
{
keyboard.AllKeys.ForEach(k => { if ( k.Note != eNote.Undefined) k.IsActiveKey = true; });
keyboard.AllKeys.ForEach(k => { if ((k.Note != eNote.Undefined) && (!keyboard.ActiveNotes.Contains(k.Note))) k.IsActiveKey = false; });
}
else
{
keyboard.AllKeys.ForEach(k => { if (k.Note != eNote.Undefined) k.IsActiveKey = true; });
}
}
Using a OneWay binding, the assignment in the OnActiveNotesChanged event handler method deletes or clears out the binding. Mike is correct saying, that the assingment is completely unnecessary, because at this point of time the value is already set before in the property. So it makes no sense at all, no matter if I use OneWay or TwoWay binding.
ActiveNotes
in its DP change handler. That would clear out a one-way binding, but not a two-way binding. You don’t need to assign a DP in its own change handler—the new value has already been applied. – Mike Strobel