绑定OneWayToSource - 奇怪的行为

时间:2013-09-13 06:02:00

标签: c# wpf

我最近对Binding Mode = OneWayToSource进行了一次测试,我仍然不知道为什么会发生某些事情。

作为示例,我在类构造函数中的dependency property上设置了一个值。现在,当Binding正在初始化时,Target属性被设置为其默认值。表示dependency property设置为null,我失去了constructor中的初始化值。

为什么会这样? Binding Mode与名称描述的方式不同。它只会更新Source而不是Target

这是代码:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = new MyViewModel();
    }

    private void OnClick(object sender, RoutedEventArgs e)
    {
        this.DataContext = new MyViewModel();
    }
}

这是XAML:

<StackPanel>
        <local:MyCustomControl Txt="{Binding Str, Mode=OneWayToSource}"/>
        <Button Click="OnClick"/>
</StackPanel>

这是MyCustomControl:

    public class MyCustomControl : Control
    {
        public static readonly DependencyProperty TxtProperty =
            DependencyProperty.Register("Txt", typeof(string), typeof(MyCustomControl), new UIPropertyMetadata(null));

        static MyCustomControl()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(MyCustomControl), new FrameworkPropertyMetadata(typeof(MyCustomControl)));
        }

        public MyCustomControl()
        {
           this.Txt = "123";
        }

        public string Txt
        {
           get { return (string)this.GetValue(TxtProperty); }

           set { this.SetValue(TxtProperty, value); }
        }
     }

这是ViewModel:

    public class MyViewModel : INotifyPropertyChanged
    {
        private string str;

        public string Str
        {
            get { return this.str; }
            set
            {
                if (this.str != value)
                {
                    this.str = value; this.OnPropertyChanged("Str");
                }
            }
         }

        protected void OnPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null && propertyName != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
     }

2 个答案:

答案 0 :(得分:4)

this.Txt = "123";

这是用本地值替换您的绑定。见dependency property value precedence。当你真正想要DependencyObject.SetValue时,你实际上是在呼叫DependencyProperty.SetCurrentValue。此外,您需要等到生命周期的后期才能执行此操作,否则WPF会更新Str两次:一次使用“123”,然后再次使用null

protected override void OnInitialized(EventArgs e)
{
    base.OnInitialized(e);
    this.SetCurrentValue(TxtProperty, "123");
}

如果在用户控件的构造函数中执行此操作,它将在WPF实例化时执行,但在WPF加载和反序列化并应用BAML时会立即替换。

更新:道歉,我误解了你的确切问题,但现在有了它的复制品,复制如下。我错过了随后更新DataContext的部分。我通过在数据上下文更改时设置当前值来修复此问题,但是在单独的消息中。否则,WPF忽略将更改转发到新数据源。

using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;

namespace SO18779291
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.setNewContext.Click += (s, e) => this.DataContext = new MyViewModel();
            this.DataContext = new MyViewModel();
        }
    }

    public class MyCustomControl : Control
    {
        public static readonly DependencyProperty TxtProperty =
            DependencyProperty.Register("Txt", typeof(string), typeof(MyCustomControl), new UIPropertyMetadata(OnTxtChanged));

        static MyCustomControl()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(MyCustomControl), new FrameworkPropertyMetadata(typeof(MyCustomControl)));
        }

        public MyCustomControl()
        {
            this.DataContextChanged += (s, e) =>
            {
                this.Dispatcher.BeginInvoke((Action)delegate
                {
                    this.SetCurrentValue(TxtProperty, "123");
                });
            };
        }

        public string Txt
        {
            get { return (string)this.GetValue(TxtProperty); }

            set { this.SetValue(TxtProperty, value); }
        }

        private static void OnTxtChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
        {
            Console.WriteLine("Changed: '{0}' -> '{1}'", e.OldValue, e.NewValue);
        }
    }

    public class MyViewModel : INotifyPropertyChanged
    {
        private string str;

        public string Str
        {
            get { return this.str; }
            set
            {
                if (this.str != value)
                {
                    this.str = value; this.OnPropertyChanged("Str");
                }
            }
        }

        protected void OnPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null && propertyName != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }
}

XAML:

<Window x:Class="SO18779291.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:SO18779291"
        Title="MainWindow" Height="350" Width="525">
    <StackPanel>
        <local:MyCustomControl Txt="{Binding Str, Mode=OneWayToSource}"/>
        <Button x:Name="setNewContext">New Context</Button>
        <TextBlock Text="{Binding Str, Mode=OneWay}"/>
    </StackPanel>
</Window>

答案 1 :(得分:0)

绑定仅在目标属性初始化后才能使用本地设置的值。

  

dependency属性设置为null,我丢失了值   为什么要在构造函数中初始化?

由于InitializeComponent()中缺少UserControl,并且您可以在Txt之前或之后设置Txt,因此我们假设假设InitializeComponent()已初始化在Txt内部。 Txt的初始化在这里意味着它获得了XAML中声明的值。如果<TextBox Text="{Binding TwoWayStr,Mode=TwoWay}"></TextBox> <local:UserControl1 Txt="{Binding OneWayToSourceStr, Mode=OneWay}" /> <Button Content="{Binding OneWayStr,Mode=OneWay}"></Button> 是预先在本地设置的,则XAML的绑定将替换此值。而且,如果事后设置,则绑定将在有机会被评估时考虑该值, TwoWay OneWayToSource 绑定都是这种情况。 (绑定评估的触发器将在后面说明。)

为了证明我的理论,我用三个具有不同绑定方式的元素进行了测试。

InitializeComponent()

但是,结果表明在两种情况下都将忽略局部值。因为与其他元素不同,在Initialized退出并触发UserControl事件时,Txt的属性尚未初始化,包括InitializeComponent()

  1. 初始化
    • (开始)在Window中输入Text ctor
    • TextBox已初始化,并且 TwoWay 绑定尝试附加绑定源
    • UserControl已初始化
    • Txt已初始化
    • Content已初始化,并且 OneWayToSource 绑定尝试附加绑定源
    • Button已初始化,并且 OneWay 绑定尝试附加绑定源
    • Window已初始化
    • InitializeComponent()已初始化
    • (结尾)退出Window中的Window
  2. 加载/渲染
    • (开始)退出Window ctor
    • TwoWay 绑定尝试在未连接的情况下附加绑定源
    • OneWayToSource 绑定尝试在未连接时附加绑定源
    • OneWay 绑定尝试在未连接的情况下附加绑定源
    • TextBox已加载
    • UserControl已加载
    • Button已加载
    • Window已加载
    • (结束)已加载所有元素
  3. 加载后
    • (开始)已加载所有元素
    • TwoWay 绑定尝试在未连接的情况下附加绑定源
    • OneWayToSource 绑定尝试在未连接时附加绑定源
    • OneWay 绑定最初尝试附加绑定源(如果未附加)
    • (结束)显示UserControl

this question中讨论了OnInitialized的这种特殊行为,即随后初始化属性。如果您使用那里提供的方法,则Initialized覆盖的调用以及BindingOperations.GetBindingExpression(this, MyCustomControl.TxtProperty)事件的触发将被延迟,直到所有属性都被初始化为止。而且,如果您在OnInitialized覆盖中或Initialized的处理程序中调用Window,则返回值将不再为null。

这时,分配本地值将是安全的。但是由于绑定源(DataContext)仍然不可用,因此绑定评估不会立即触发以传输该值,请注意直到Status初始化之后才设置DataContext。实际上,如果您检查返回的绑定表达式的Unattached属性,则该值为Txt

进入加载阶段后,第二次尝试附加绑定源将抓住DataContext,然后第一次绑定源的附加将触发评估,其中Str(在这种情况下为“ 123”)的值通过设置器转移到源属性Active。现在,此biniding表达式的状态更改为InitializeComponent(),表示绑定源的已解析状态。

如果您不使用该问题中提到的方法,则可以将Window的Intialized之后的局部值赋值移到Window或{{1 }} Loaded / Window的处理程序,结果将相同。除非在UserControl中进行了设置,否则本地分配将触发立即评估,因为绑定源已经附加。而由第一个附件触发的触发器将改为传输默认值Loaded

通过更改绑定源触发的OneWayToSource绑定评估会将默认值传递给source属性。

  

如果我在运行时更改了DataContext怎么办?它会再次摧毁   控制中Dependecy属性的值。

在上一节中,我们已经看到两种Txt绑定评估的触发器,一种是目标属性更改(如果绑定的OneWayToSourceUpdateSourceTrigger,通常是默认设置),另一个是绑定源的第一个附件。

从接受的答案中的讨论看来,您还有第二个问题,即在绑定源更改触发的评估中,为什么使用默认值而不是PropertyChanged的“当前”值。事实证明,这是第三种评估触发器的设计行为,这一点也得到了this question的第二和第三答案的证实。顺便说一下,我正在.Net 4.5中对此进行测试,通过在setter之后删除getter调用,使Txt的评估过程从4.0发生了变化,但这不会改变“默认值”的行为。 / p>

请注意,对于 TwoWay OneWay 绑定,由第一次附加和绑定源更改触发的评估通过调用getter行为完全相同。

额外:OneWayToSource绑定将忽略路径所有级别上的更改

OneWayToSource绑定的另一个奇怪行为可能与该主题有关,这是尽管预期如果绑定路径包含多个级别,则不会监听目标属性的更改,这意味着目标属性是嵌套的,从目标属性到所有级别的更改也将被忽略。例如,如果像这样OneWayToSource声明绑定,那么Text={Binding ChildViewModel.Str, Mode=OneWayToSource}属性的更改将不会触发绑定评估,实际上,如果您通过更改ChildViewModel,{在先前的Text实例上调用{1}} setter。这种行为使Str与其他两种模式的偏离更大。

P.S .:我知道这是旧帖子。但是,由于这些行为仍然没有得到充分的记录,因此我认为这对尝试了解发生的情况的人也可能会有帮助。