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.
Dependency properties have a complex system of precedence. The value of a dependency property at any given time may come from various sources: bindings, style setters, trigger setters, etc. Local values have the highest priority, and when you set a local value, you suppress values coming from other sources.
In the case of a binding, setting a local value will cause a source-to-target binding (
OneWay
orOneTime
) to be *removed*. However, when you set a local value on a property with a target-to-source binding (TwoWay
orOneWayToSource
), the binding will be maintained, and the local value you assigned will get propagated back to the source.In your case, the issue is here:
In your
OnActiveNotesChanged
handler, you're assigning a new local value toActiveNotes
, which is causing yourOneWay
binding to be removed. Fortunately, the solution is simple: you can remove this line entirely, as it is redundant. In most cases it is unnecessary to assign a dependency property in its own change handler—the new value has already been applied. (And if you want an opportunity to replace a proposed value before it gets applied, the place to do that would be aCoerceValueCallback
, which you can also specify in youPropertyMetadata
.)