C# 绑定OneWayToSource-奇怪的行为

C# 绑定OneWayToSource-奇怪的行为,c#,wpf,C#,Wpf,最近我用Binding Mode=OneWayToSource做了一系列测试,但我仍然不知道为什么会发生某些事情 例如,我在类构造函数中的依赖项属性上设置了一个值。现在,当绑定初始化时,Target属性被设置为其默认值。表示依赖项属性设置为null,并且我丢失了在构造函数中初始化的值 为什么会这样?绑定模式的工作方式与名称描述的方式不同。它只能更新源代码,而不能更新目标代码 以下是代码: public partial class MainWindow : Window { public

最近我用
Binding Mode=OneWayToSource
做了一系列测试,但我仍然不知道为什么会发生某些事情

例如,我在类构造函数中的
依赖项属性上设置了一个值。现在,当绑定初始化时,
Target
属性被设置为其默认值。表示
依赖项属性
设置为
null
,并且我丢失了在
构造函数
中初始化的值

为什么会这样?
绑定模式
的工作方式与名称描述的方式不同。它只能更新源代码,而不能更新目标代码

以下是代码:

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>
这是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;
     }
这将用本地值替换绑定。看见你实际上是在你真正想要的时候打电话。此外,您需要等到生命周期的后期才能执行此操作,否则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:


新语境
只有在初始化目标属性后,绑定才会使用本地设置的值。 dependency属性被设置为null,并且我丢失了值I 在构造函数中初始化。为什么会发生这种情况

自从<>代码>初始化代码>(或代码>在您的代码> UserControl < /代码> cTor中丢失,您可以在它之前或之后设置<代码> txt < /代码>,让我们考虑两种情况,假定“代码> txt<代码>在RealAlgEnCyMoMutual())/>代码>中初始化。在这里,
Txt
的初始化意味着它被分配到XAML中声明的值。如果事先在本地设置了
Txt
,则来自XAML的绑定将替换此值。如果之后设置了该值,则每当有机会对该值进行求值时,绑定都会考虑该值,这对于双向和单向ToSource绑定都是如此。(绑定评估的触发将在后面解释。)

为了证明我的理论,我用三个元素做了一个测试,每个元素都有不同的绑定模式

<TextBox Text="{Binding TwoWayStr,Mode=TwoWay}"></TextBox>
<local:UserControl1 Txt="{Binding OneWayToSourceStr, Mode=OneWay}" />
<Button Content="{Binding OneWayStr,Mode=OneWay}"></Button>   

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

  • 初始化
    • (开始)在
      窗口中输入
      初始化组件()
    • Text
      初始化并双向绑定尝试附加绑定源
    • 文本框
      已初始化
    • UserControl
      已初始化
    • Txt
      已初始化且OneWayToSource绑定尝试附加绑定源
    • Content
      已初始化且单向绑定尝试附加绑定源
    • 按钮
      已初始化
    • 窗口
      已初始化
    • (结束)退出
      窗口中的
      初始化组件()
  • 加载/渲染
    • (开始)退出
      窗口
    • 双向绑定在未附加的情况下尝试附加绑定源
    • OneWayToSource绑定如果未附加,则尝试附加绑定源
    • 单向绑定在未附加的情况下尝试附加绑定源
    • 窗口
      已加载
    • 文本框
      已加载
    • UserControl
      已加载
    • 按钮
      已加载
    • (完)已加载所有元素
  • 后装
    • (开始)加载所有元素
    • 双向绑定在未附加的情况下尝试附加绑定源
    • OneWayToSource绑定如果未附加,则尝试附加绑定源
    • 单向绑定未附加时附加绑定源的初始尝试
    • (结束)
      窗口显示
  • 中讨论了
    UserControl
    的这种特殊行为,这些行为之后会初始化属性。如果使用此处提供的方法,则调用
    OnInitialized
    覆盖以及触发
    Initialized
    事件将延迟到所有属性初始化之后。如果在
    OnInitialized
    override或
    Initialized
    的处理程序中调用
    BindingOperations.GetBindingExpression(这是MyCustomControl.TxtProperty)
    ,则返回值将不再为null

    此时,可以安全地指定局部值。但是绑定求值不会立即触发以传输值,因为绑定源(DataContext)仍然不可用,请注意,DataContext在
    窗口
    初始化之后才设置。事实上,如果检查返回的绑定表达式的
    Status
    属性,则该值为
    Unattached

    进入加载阶段后,第二次尝试附加绑定源将占用DataContext,然后将由绑定源的第一次附加触发计算,其中
    Txt
    (在本例中为“123”)的值将通过setter传输到源属性
    Str
    。这个biniding表达式的状态现在变为
    Active
    ,表示绑定源的解析状态

    如果不使用该问题中提到的方法,可以将局部值移动到assig
    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;
        }
    }
    
    <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>
    
    <TextBox Text="{Binding TwoWayStr,Mode=TwoWay}"></TextBox>
    <local:UserControl1 Txt="{Binding OneWayToSourceStr, Mode=OneWay}" />
    <Button Content="{Binding OneWayStr,Mode=OneWay}"></Button>