WPF:TwoWay绑定始终更新 - OneWay绑定仅更新

时间:2017-12-01 00:40:31

标签: c# wpf xaml binding oneway

我知道你的想法:现在是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%相同,则目标对象中的ActivesNotes属性仅更新,即使PropertyChanged事件也是如此被解雇了。我检查并仔细检查了一下。始终触发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绑定都没有任何意义。

2 个答案:

答案 0 :(得分:5)

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

如果是绑定,设置本地值将导致源到目标绑定(ern code-push <miniapps..> Options: --help Show help [boolean] --descriptor, -d Full native application selector (target native application version for the push) --force, -f Force upgrade (ignore compatibility issues -at your own risks-) --appName Application name --deploymentName Deployment to release the update to [string] --platform, -p Platform name (android / ios) [string] --targetBinaryVersion, -t Semver expression that specifies the binary app version(s) this release is targeting (e.g. 1.1.0, ~1.2.3) [string] --mandatory, -m Specifies whether this release should be considered mandatory [default: false] --rollout, -r Percentage of users this release should be immediately available to [string] [default: "100%"] --skipConfirmation, -s Skip final confirmation prompt if no compatibility issues are detected More info about this command @ https://electrode.gitbooks.io/electrode-native/content/cli/code-push.html OneWay *删除* 。但是,当您在具有目标到源绑定(OneTimeTwoWay)的属性上设置本地值时,将保持绑定,并且您分配的本地值将传播回来源。

在您的情况下,问题在于:

OneWayToSource

keyboard.ActiveNotes = (ReadOnlyCollection<eNote>)e.NewValue; 处理程序中,您需要为OnActiveNotesChanged分配新的本地值,这会导致您的ActiveNotes绑定被删除。幸运的是,解决方案很简单:您可以完全删除此行,因为它是多余的。在大多数情况下,不必在自己的更改处理程序中分配依赖项属性 - 已经应用了新值。 (如果您希望有机会在应用之前替换建议的值,那么这样做的地方将是OneWay,您也可以在CoerceValueCallback中指定。)

答案 1 :(得分:1)

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

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

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

事实上,您还必须注意,如果 CustomControl 声明了 DependencyPropertyBinding 中的消费者都使用了 CustomControl {1}} 和 CustomControl模板,那么您必须确保模板中的绑定 CustomControlTemplateBinding 类型的八进制或使用 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);
}

如果没有,更新时Binding的模板中使用的UserControl也会为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}"