我最近对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;
}
答案 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()
。
Window
中输入Text
ctor TextBox
已初始化,并且 TwoWay 绑定尝试附加绑定源UserControl
已初始化Txt
已初始化Content
已初始化,并且 OneWayToSource 绑定尝试附加绑定源Button
已初始化,并且 OneWay 绑定尝试附加绑定源Window
已初始化InitializeComponent()
已初始化Window
中的Window
Window
ctor TextBox
已加载UserControl
已加载Button
已加载Window
已加载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
。
如果我在运行时更改了DataContext怎么办?它会再次摧毁 控制中Dependecy属性的值。
在上一节中,我们已经看到两种Txt
绑定评估的触发器,一种是目标属性更改(如果绑定的OneWayToSource
是UpdateSourceTrigger
,通常是默认设置),另一个是绑定源的第一个附件。
从接受的答案中的讨论看来,您还有第二个问题,即在绑定源更改触发的评估中,为什么使用默认值而不是PropertyChanged
的“当前”值。事实证明,这是第三种评估触发器的设计行为,这一点也得到了this question的第二和第三答案的证实。顺便说一下,我正在.Net 4.5中对此进行测试,通过在setter之后删除getter调用,使Txt
的评估过程从4.0发生了变化,但这不会改变“默认值”的行为。 / p>
请注意,对于 TwoWay 和 OneWay 绑定,由第一次附加和绑定源更改触发的评估通过调用getter行为完全相同。
OneWayToSource
绑定的另一个奇怪行为可能与该主题有关,这是尽管预期如果绑定路径包含多个级别,则不会监听目标属性的更改,这意味着目标属性是嵌套的,从目标属性到所有级别的更改也将被忽略。例如,如果像这样OneWayToSource
声明绑定,那么Text={Binding ChildViewModel.Str, Mode=OneWayToSource}
属性的更改将不会触发绑定评估,实际上,如果您通过更改ChildViewModel
,{在先前的Text
实例上调用{1}} setter。这种行为使Str
与其他两种模式的偏离更大。
P.S .:我知道这是旧帖子。但是,由于这些行为仍然没有得到充分的记录,因此我认为这对尝试了解发生的情况的人也可能会有帮助。