我知道你的想法:2017年,请不要再提出这个,但我真的找不到任何有价值的解释.
请查看此XAML代码中的ActiveNotes属性.
我在我的XAML中有这个TwoWay绑定,它完美地运行.如果触发了ScaleNotes的PropertyChanged事件并且绑定设置为TwoWay,则它始终更新.
<c:Keyboard
Grid.Row="2"
Grid.Column="0"
PlayCommand="{Binding PlayCommand}"
StopCommand="{Binding StopCommand}"
ActiveNotes="{Binding ScaleNotes, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
ViewModel中的ScaleNotes属性如下所示.每当它发生变化时,都会保证触发PropertyChanged事件.我检查并仔细检查了一下. ViewModel中的业务逻辑有效.
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;
}
到这里一切都很好.每当更改VM中的ScaleNotes属性时,都会更新目标属性ActiveNotes.
现在的问题是:
如果我只将XAML中的绑定更改为OneWay并且VM中的业务逻辑保持100%相同,则即使触发了PropertyChanged事件,目标对象中的ActivesNotes属性也只更新一次.我检查并仔细检查了一下.始终触发ScaleNotes属性的PropertyChanged事件.
<c:Keyboard
Grid.Row="2"
Grid.Column="0"
PlayCommand="{Binding PlayCommand}"
StopCommand="{Binding StopCommand}"
ActiveNotes="{Binding ScaleNotes, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
只是为了完成这个,这里是目标对象中的DP.
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; });
}
}
我不明白这一点.据我所知,OneWay和TwoWay只定义值更新的方向,而不是更新它们的频率.
我无法理解,一切都运行良好的TwoWay,业务逻辑保持100%相同,OneWay是一个交易破坏者.
如果你问自己,为什么我想知道这个:这个绑定计划为OneWay绑定.以任何方式更新源都没有意义.我只将它改为TwoWay,因为OneWay没有按预期工作.
在@MikeStrobel的帮助下解决方案:(见评论)
代码需要以这种方式更改:
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; });
}
}
使用OneWay绑定,OnActiveNotesChanged事件处理程序方法中的赋值将删除或清除绑定.迈克是正确的说,这种分配是完全没必要的,因为在这个时间点,价值已经在物业中设定.因此,无论我使用OneWay还是TwoWay绑定,它都毫无意义.
解决方法:
依赖项属性具有precedence的复杂系统.任何给定时间的依赖项属性的值可能来自各种来源:绑定,样式设置器,触发器设置器等.本地值具有最高优先级,并且当您设置本地值时,你压制来自其他来源的价值.
在绑定的情况下,设置本地值将导致*删除源到目标绑定(OneWay或OneTime)*.但是,当您使用目标到源绑定(TwoWay或OneWayToSource)在属性上设置本地值时,将保持绑定,并且您指定的本地值将传播回源.
在您的情况下,问题出在这里:
keyboard.ActiveNotes = (ReadOnlyCollection<eNote>)e.NewValue;
在OnActiveNotesChanged处理程序中,您将为ActiveNotes分配一个新的本地值,这会导致您的OneWay绑定被删除.幸运的是,解决方案很简单:您可以完全删除此行,因为它是多余的.在大多数情况下,不必在自己的更改处理程序中分配依赖项属性 – 已经应用了新值. (如果您希望有机会在应用之前替换建议值,那么执行此操作的位置将是CoerceValueCallback,您也可以在PropertyMetadata中指定.)