簡體   English   中英

綁定 OneWayToSource - 奇怪的行為

[英]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 ,讓我們考慮這兩種情況,假設TxtInitializeComponent()InitializeComponent() 這里Txt初始化意味着它被分配了在 XAML 中聲明的值。 如果事先在本地設置了Txt ,則來自 XAML 的綁定將替換此值。 如果它是在之后設置的,則只要有機會被評估,綁定就會考慮這個值, TwoWayOneWayToSource綁定都是這種情況。 (綁定評估的觸發將在后面解釋。)

為了證明我的理論,我用三個元素進行了測試,每個元素都有不同的綁定模式。

<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

  1. 初始化
    • (開始)在Window ctor中進入InitializeComponent()
    • Text初始化和雙向綁定嘗試附加綁定源
    • TextBox初始化的TextBox
    • UserControl初始化
    • Txt初始化和OneWayToSource綁定嘗試附加綁定源
    • Content初始化和OneWay綁定嘗試附加綁定源
    • Button初始化
    • Window初始化
    • (完)退出Window ctor中的InitializeComponent()
  2. 加載/渲染
    • (開始)退出Window構造函數
    • 如果未附加,則雙向綁定嘗試附加綁定源
    • OneWayToSource綁定嘗試在未附加的情況下附加綁定源
    • OneWay綁定嘗試附加綁定源(如果未附加)
    • Window加載
    • 已加載TextBox
    • UserControl已加載
    • Button已加載
    • (完)所有元素加載完畢
  3. 后加載
    • (開始)加載所有元素
    • 如果未附加,則雙向綁定嘗試附加綁定源
    • OneWayToSource綁定嘗試在未附加的情況下附加綁定源
    • OneWay綁定初始嘗試附加綁定源(如果未附加)
    • (完) Window顯示

這個問題中討論了UserControl這種特殊行為,即屬性隨后被初始化。 如果您使用那里提供的方法,則OnInitialized覆蓋的調用以及Initialized事件的觸發將被延遲到所有屬性都被初始化之后。 如果您在OnInitialized覆蓋或Initialized的處理程序中調用BindingOperations.GetBindingExpression(this, MyCustomControl.TxtProperty) ,則返回值將不再為空。

此時,分配本地值將是安全的。 但是綁定評估不會立即觸發傳遞值,因為綁定源(DataContext)仍然不可用,注意DataContext直到Window初始化之后才設置。 事實上,如果您檢查返回的綁定表達式的Status屬性,則值為Unattached

進入 Loading 階段后,第二次嘗試附加綁定源將占用 DataContext,然后將通過綁定源的第一個附件觸發評估,其中Txt值(在本例中為“123”)將通過以下方式傳輸到源屬性Str二傳手。 並且此綁定表達式的狀態現在更改為Active ,表示綁定源的解析狀態。

如果你不使用在問題中提到的方法,你可以移動后局部值分配InitializeComponent()窗口,進入Intialized的處理程序WindowLoaded的處理程序Window / UserControl ,其結果將是相同的。 除非它在Loaded中設置,否則本地分配將觸發立即評估,因為綁定源已經附加。 而第一個附件觸發的將改為傳輸Txt默認值。

由綁定源更改觸發的 OneWayToSource 綁定評估會將默認值傳遞給源屬性。

如果我在運行時更改 DataContext 會怎樣? 它會再次破壞控制中的dependecy 屬性的值。

在上一節中,我們已經看到了OneWayToSource綁定的綁定評估的兩種觸發器,一種是目標屬性更改(如果綁定的UpdateSourceTriggerPropertyChanged ,通常是默認的),另一種是綁定源的第一個附件。

從已接受的答案中的討論看來,您有第二個問題,即為什么在綁定源更改觸發的評估中使用默認值而不是Txt的“當前”值。 事實證明,這是第三種評估觸發器的設計行為, 這個問題的第二個和第三個答案也證實了這一點 順便說一下,我正在 .Net 4.5 中對此進行測試,通過在 setter 之后刪除 getter 調用, OneWayToSource的評估過程從 4.0 發生了變化,但這不會改變“默認值”行為。

作為旁注,對於TwoWayOneWay綁定,由第一個附件觸發的評估和綁定源的更改通過調用 getter 的行為完全相同。

額外:OneWayToSource 綁定將忽略所有級別的路徑更改

OneWayToSource綁定的另一個奇怪行為可能與該主題有關,盡管預期不會偵聽目標屬性的更改,但如果綁定路徑包含多個級別,這意味着目標屬性是嵌套的,則更改為所有從目標屬性升級的級別也被忽略。 例如,如果綁定是這樣聲明的Text={Binding ChildViewModel.Str, Mode=OneWayToSource} ,更改ChildViewModel屬性不會觸發綁定評估,實際上,如果您通過更改TextStr setter 觸發評估在前一個ChildViewModel實例上調用。 這種行為使OneWayToSource與其他兩種模式的偏差更大。

PS:我知道這是一個舊帖子。 但是由於這些行為仍然沒有得到很好的記錄,我認為這可能對任何試圖了解會發生什么的人都有幫助。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM