WPF:双向绑定始终更新 - 单向绑定仅更新一次



我知道你的想法:现在是2017年,请不要再想出这个了,但我真的找不到任何有价值的解释。

请查看此 XAML 代码中的ActiveNotes属性。

我的 XAML 中有这个双向绑定,它运行良好。如果触发了 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 事件。我检查并仔细检查了它。视图模型中的业务逻辑有效。

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 是一个交易破坏者。

如果你问自己,为什么我想知道这个:这个绑定是计划作为单向绑定的。以任何方式更新源是没有意义的。我只将其更改为 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 事件处理程序方法中的赋值将删除或清除绑定。Mike 说得对,断言是完全没有必要的,因为此时该值已经在属性中设置好了。所以无论我使用 OneWay 还是 TwoWay 绑定,这都没有意义。

依赖项属性具有复杂的优先级系统。 在任何给定时间,依赖项属性的值可能来自各种来源:绑定、样式资源库、触发器资源库等。 本地值具有最高优先级,设置本地值时,将抑制来自其他源的值。

在绑定的情况下,设置本地值将导致源到目标绑定(OneWayOneTime)被*删除*。 但是,当您在具有目标到源绑定(TwoWayOneWayToSource)的属性上设置本地值时,将保留绑定,并且分配的本地值将传播回源。

在您的情况下,问题在这里:

keyboard.ActiveNotes = (ReadOnlyCollection<eNote>)e.NewValue;

OnActiveNotesChanged处理程序中,您将为ActiveNotes分配一个新的本地值,这会导致删除OneWay绑定。 幸运的是,解决方案很简单:您可以完全删除此行,因为它是多余的。 在大多数情况下,没有必要在其自己的更改处理程序中分配依赖项属性 - 新值已应用。 (如果您希望有机会在应用建议的值之前对其进行替换,则执行此操作的位置将是CoerceValueCallback,您也可以在PropertyMetadata中指定。

让我分享一下我在这个问题上度过不眠之夜后的经验!

我在一个稍微不同的用例中偶然发现了这个问题,我找不到解决方案,但多亏了上面的建议,我终于可以解决它了。

简而言之,我面临着同样的绑定解离问题,但根本没有代码来访问有罪的财产!问题发生在CustomControl

事实上,您还必须意识到,如果CustomControl声明了一个DependencyProperty,该在CustomControl使用者CustomControl模板内部的Binding中使用,那么您必须确保我们在CustomControl模板中的绑定TemplateBinding类型的八重或使用OneWayOneWayToSource的常规绑定。

public static readonly DependencyProperty IsCheckedProperty = DependencyProperty.Register("IsChecked", typeof(bool), typeof(QAToggle), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.None));
public bool IsChecked
{
get => (bool)this.GetValue(IsCheckedProperty);
set => this.SetValue(IsCheckedProperty, value);
}

如果没有,更新时UserControl模板中使用的Binding也会为CustomControl的属性设置一个新值,从而删除CustomControl的使用者设置的外部绑定。

<Style TargetType="{x:Type ccont:QAToggle}">

<Setter Property="SnapsToDevicePixels"  Value="true" />
<Setter Property="MinHeight"            Value="23" />
<Setter Property="MinWidth"             Value="75" />

<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ccont:QAToggle}">
<ToggleButton x:Name            = "QAToggle"
Command           = "{TemplateBinding Command}" 
CommandParameter  = "{TemplateBinding CommandParameter}" 
FontSize          = "{TemplateBinding FontSize}"
FontWeight        = "{TemplateBinding FontWeight}"
FontFamily        = "{TemplateBinding FontFamily}"
IsThreeState      = "False"
IsChecked         = "{Binding IsChecked, RelativeSource={RelativeSource AncestorType=ccont:QAToggle}}">

在这里,IsChecked = "{Binding IsChecked, RelativeSource={RelativeSource AncestorType=ccont:QAToggle}}"行将破坏外部绑定。为避免这种情况,请将其更改为:

IsChecked = "{TemplateBinding IsChecked}"

IsChecked = "{Binding IsChecked, RelativeSource={RelativeSource AncestorType=ccont:QAToggle}, Mode=OneWay}"

相关内容

  • 没有找到相关文章

最新更新