[英]Binding OneWayToSource - Strange Behavior
我最近用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); }
}
}
這是視圖模型:
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;
}
this.Txt = "123";
這是用本地值替換您的綁定。 請參閱依賴屬性值優先級。 你基本上調用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>
依賴屬性被設置為 null 並且我丟失了我在構造函數中初始化的值。為什么會發生這種情況?
由於您的UserControl
構造函數中缺少InitializeComponent()
,您可以在它之前或之后設置Txt
,讓我們考慮這兩種情況,假設Txt
在InitializeComponent()
內InitializeComponent()
。 這里Txt
初始化意味着它被分配了在 XAML 中聲明的值。 如果事先在本地設置了Txt
,則來自 XAML 的綁定將替換此值。 如果它是在之后設置的,則只要有機會被評估,綁定就會考慮這個值, TwoWay和OneWayToSource綁定都是這種情況。 (綁定評估的觸發將在后面解釋。)
為了證明我的理論,我用三個元素進行了測試,每個元素都有不同的綁定模式。
<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
。
Window
ctor中進入InitializeComponent()
Text
初始化和雙向綁定嘗試附加綁定源TextBox
初始化的TextBox
UserControl
初始化Txt
初始化和OneWayToSource綁定嘗試附加綁定源Content
初始化和OneWay綁定嘗試附加綁定源Button
初始化Window
初始化Window
ctor中的InitializeComponent()
Window
構造函數Window
加載TextBox
UserControl
已加載Button
已加載Window
顯示在這個問題中討論了UserControl
這種特殊行為,即屬性隨后被初始化。 如果您使用那里提供的方法,則OnInitialized
覆蓋的調用以及Initialized
事件的觸發將被延遲到所有屬性都被初始化之后。 如果您在OnInitialized
覆蓋或Initialized
的處理程序中調用BindingOperations.GetBindingExpression(this, MyCustomControl.TxtProperty)
,則返回值將不再為空。
此時,分配本地值將是安全的。 但是綁定評估不會立即觸發傳遞值,因為綁定源(DataContext)仍然不可用,注意DataContext直到Window
初始化之后才設置。 事實上,如果您檢查返回的綁定表達式的Status
屬性,則值為Unattached
。
進入 Loading 階段后,第二次嘗試附加綁定源將占用 DataContext,然后將通過綁定源的第一個附件觸發評估,其中Txt
值(在本例中為“123”)將通過以下方式傳輸到源屬性Str
二傳手。 並且此綁定表達式的狀態現在更改為Active
,表示綁定源的解析狀態。
如果你不使用在問題中提到的方法,你可以移動后局部值分配InitializeComponent()
窗口,進入Intialized
的處理程序Window
或Loaded
的處理程序Window
/ UserControl
,其結果將是相同的。 除非它在Loaded
中設置,否則本地分配將觸發立即評估,因為綁定源已經附加。 而第一個附件觸發的將改為傳輸Txt
默認值。
如果我在運行時更改 DataContext 會怎樣? 它會再次破壞控制中的dependecy 屬性的值。
在上一節中,我們已經看到了OneWayToSource
綁定的綁定評估的兩種觸發器,一種是目標屬性更改(如果綁定的UpdateSourceTrigger
為PropertyChanged
,通常是默認的),另一種是綁定源的第一個附件。
從已接受的答案中的討論看來,您有第二個問題,即為什么在綁定源更改觸發的評估中使用默認值而不是Txt
的“當前”值。 事實證明,這是第三種評估觸發器的設計行為, 這個問題的第二個和第三個答案也證實了這一點。 順便說一下,我正在 .Net 4.5 中對此進行測試,通過在 setter 之后刪除 getter 調用, OneWayToSource
的評估過程從 4.0 發生了變化,但這不會改變“默認值”行為。
作為旁注,對於TwoWay和OneWay綁定,由第一個附件觸發的評估和綁定源的更改通過調用 getter 的行為完全相同。
OneWayToSource
綁定的另一個奇怪行為可能與該主題有關,盡管預期不會偵聽目標屬性的更改,但如果綁定路徑包含多個級別,這意味着目標屬性是嵌套的,則更改為所有從目標屬性升級的級別也被忽略。 例如,如果綁定是這樣聲明的Text={Binding ChildViewModel.Str, Mode=OneWayToSource}
,更改ChildViewModel
屬性不會觸發綁定評估,實際上,如果您通過更改Text
、 Str
setter 觸發評估在前一個ChildViewModel
實例上調用。 這種行為使OneWayToSource
與其他兩種模式的偏差更大。
PS:我知道這是一個舊帖子。 但是由於這些行為仍然沒有得到很好的記錄,我認為這可能對任何試圖了解會發生什么的人都有幫助。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.