【问题标题】:WPF: TwoWay binding is ALWAYS updated - OneWay binding is ONLY ONCE updatedWPF:TwoWay 绑定始终更新 - OneWay 绑定仅更新一次
【发布时间】:2018-05-15 01:26:47
【问题描述】:

我知道你的想法:现在是 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 的帮助下解决:(参见 cmets)

代码需要这样改:

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 绑定,这都毫无意义。

【问题讨论】:

  • 看起来您在其 DP 更改处理程序中为 ActiveNotes 设置了一个新的本地值。这将清除单向绑定,但不会清除双向绑定。您无需在其自己的更改处理程序中分配 DP — 新值已被应用。
  • 我会测试它。请给我 2 分钟。
  • @Mike Strobel 这是正确答案。如果我删除 DP OnActiveNotesChanged 中的分配,它会按预期与 OneWay 一起工作。请您在回答中解释一下“清除”是什么意思。这是否意味着,它删除了绑定?
  • @MikeStrobel 你已经给出了正确的答案。非常感谢,你让我很开心。如果你喜欢,就写一个单独的答案,这样我就可以给你积分/积分。

标签: c# wpf xaml binding oneway


【解决方案1】:

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

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

简而言之,我遇到了相同的绑定分离问题,但根本没有代码访问被指控的属性!问题发生在CustomControl

实际上,您还必须注意,如果CustomControl 声明了DependencyPropertyCustomControl消费者 和 CustomControl 的strong>模板,那么您必须确保我们在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 也会为CustomControls 属性设置一个新值,从而删除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}"

【讨论】:

    【解决方案2】:

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

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

    在你的情况下,问题就在这里:

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

    在您的OnActiveNotesChanged 处理程序中,您为ActiveNotes 分配了一个新的本地值,这导致您的OneWay 绑定被删除。幸运的是,解决方案很简单:您可以完全删除这条线,因为它是多余的。在大多数情况下,没有必要在其自己的更改处理程序中分配依赖属性——新值已被应用。 (如果您想在提议的值被应用之前有机会替换它,那么可以使用CoerceValueCallback,您也可以在PropertyMetadata 中指定它。)

    【讨论】:

    • 绝妙的答案!谢谢你,迈克。你让我今天一整天都感觉很好。同时我也觉得有点惭愧,因为我觉得这是一个初学者的问题。然而——向纽约致敬!
    • 无需感到羞耻。 WPF 的学习曲线非常陡峭。阅读我链接到的 MSDN 文章将使您了解一些微妙之处。无论如何,很高兴我能提供帮助。
    • 正如您在问题下的 cmets 中已经提到的:此代码行从一开始就没有任何意义。无论我使用 OneWay 还是 TwoWay 绑定,因为在 OnChanged 事件处理程序中,新值总是之前已经设置好了。
    猜你喜欢
    • 2020-01-16
    • 1970-01-01
    • 2019-01-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多