簡體   English   中英

當DataContext發生更改時,WPF綁定OneWayToSource將source屬性設置為“”

[英]WPF binding OneWayToSource sets source property to “” when the DataContext is changed

我有一個OneWayToSource綁定,當我設置目標控件的DataContext時,它的行為不像我預期的那樣。 源的屬性設置為默認值而不是目標控件的屬性值。

我在標准的WPF窗口中創建了一個非常簡單的程序來說明我的問題:

XAML

<StackPanel>
  <TextBox x:Name="tb"
    Text="{Binding Path=Text,Mode=OneWayToSource,UpdateSourceTrigger=PropertyChanged}"
    TextChanged="TextBox_TextChanged"/>

  <Button Content="Set DataContext" Click="Button1_Click"/>
</StackPanel>

MainWindow.cs

public partial class MainWindow : Window
{
   private ViewModel _vm = new ViewModel();

   private void Button1_Click(object sender, RoutedEventArgs e)
   {
      Debug.Print("'Set DataContext' button clicked");
      tb.DataContext = _vm;
   }

   private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
   {
      Debug.Print("TextBox changed to " + tb.Text);
   }
}

ViewModel.cs

public class ViewModel
{
   private string _Text;
   public string Text
   {
      get { return _Text; }
      set
      {
         Debug.Print(
            "ViewModel.Text (old value=" + (_Text ?? "<null>") + 
            ", new value=" + (value ?? "<null>") + ")");
         _Text = value;
      }
   }
}

TextBox tb以null DataContext開頭,因此不希望綁定做任何事情。 因此,如果我在文本框中鍵入內容,例如“X”,則ViewModel.Text屬性保持為null。

如果我然后單擊Set DataContext按鈕,我希望ViewModel.Text屬性設置為TextBox.Text屬性的“X”。 而是設置為“”。 當然綁定工作正常,因為如果我在文本框中輸入“Y”,在“X”之后,它將ViewModel.Text屬性設置為“XY”。

以下是輸出的示例(由於評估的順序,最后兩行是反直覺的,但它們肯定都在鍵入“Y”后立即出現):

TextBox更改為X.
單擊“設置DataContext”按鈕
ViewModel.Text(舊值= <null>,新值=)
ViewModel.Text(舊值=,新值= XY)
TextBox更改為XY

為什么在設置DataContext時將ViewModel.Text屬性設置為“”而不是“X”?

我究竟做錯了什么? 我錯過了什么嗎? 我誤解了綁定方面的問題嗎?

編輯 :我原以為輸出是:

TextBox更改為X.
單擊“設置DataContext”按鈕
ViewModel.Text(舊值= <null>,新值= X
ViewModel.Text(舊值= X ,新值= XY)
TextBox更改為XY

它不是一個bug或perhabs。 微軟聲稱它的設計。 首先輸入x然后通過單擊Button來殺死DataContext,這樣為什么TextBox保持x並且你的viewModel.Text屬性被新初始化(它為空)。 當在datacontext上更改時,仍會調用getter。 最后你沒有機會解決這個問題。

然而,你可以使用兩種方式讓它成為現實。

TextBox在其TextProperty中有一個Binding,當你設置TextBox的DataContext時,TextBox將更新它的源(viewmodel.Text),無論UpdateSourceTrigger是哪種類型。

據說viewmodel中的第一個輸出

ViewModel.Text (old value=<null>, new value=)

不是由UpdateSourceTrigger=PropertyChanged觸發的。

這只是一個init的過程:

private string _Text;
public string Text
{
    get { return _Text; }
    set
    {
        Debug.Print(
           "ViewModel.Text (old value=" + (_Text ?? "<null>") +
           ", new value=" + (value ?? "<null>") + ")");
        _Text = value;
    }
}

因為它不是由UpdateSourceTrigger=PropertyChanged觸發的,所以viewmodel將不知道TextBox.Text的值。

當您鍵入“Y”時,PropertyChanged的觸發器將起作用,因此viewmodel讀取TextBox的文本。

在這里你將需要UpdateSource如下所示:

 private void Button1_Click(object sender, RoutedEventArgs e)
   {

      Debug.Print("'Set DataContext' button clicked");
      tb.DataContext = _vm;
      var bindingExp = tb.GetBindingExpression(TextBox.TextProperty);
      bingExp.UpdateSource();
   }

.NET 4中有一個錯誤,它有一種方法可以為OneWayToSource綁定調用getter,這就是為什么你遇到這個問題。你可以通過在tb.DataContext = _vm上放置斷點來驗證它。 你會發現setter被調用,就在Text屬性上調用getter之后。你可以通過在分配datacontext之前手動從視圖中提供viewmodel值來解決你的問題.NET 4.5解決了這個問題。 看到這里這里

private void Button1_Click(object sender, RoutedEventArgs e)
{
   Debug.Print("'Set DataContext' button clicked");       
    _vm.Text=tb.Text;
    tb.DataContext = _vm;
}

你需要Attached property

public static readonly DependencyProperty OneWaySourceRaiseProperty = DependencyProperty.RegisterAttached("OneWaySourceRaise", typeof(object), typeof(FrameworkElementExtended), new FrameworkPropertyMetadata(OneWaySourceRaiseChanged));

        public static object GetOneWaySourceRaise(DependencyObject o)
        {
            return o.GetValue(OneWaySourceRaiseProperty);
        }

        public static void SetOneWaySourceRaise(DependencyObject o, object value)
        {
            o.SetValue(OneWaySourceRaiseProperty, value);
        }

        private static void OneWaySourceRaiseChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (e.NewValue == null)
                return;

            var target = (FrameworkElement)d;
            target.Dispatcher.InvokeAsync(() =>
        {
            var bindings = target.GetBindings().Where(i => i.ParentBinding?.Mode == BindingMode.OneWayToSource).ToArray();
            foreach (var i in bindings)
            {
                i.DataItem.SetProperty(i.ParentBinding.Path.Path, d.GetValue(i.TargetProperty));
            }
        });

並在XAML中設置綁定:

extendends:FrameworkElementExtended.OneWaySourceRaise="{Binding}"

其中{Binding} - 綁定到DataContext 你需要:

    public static IEnumerable<BindingExpression> GetBindings<T>(this T element, Func<DependencyProperty, bool> func = null) where T : DependencyObject
            {
                var properties = element.GetType().GetDependencyProperties();
                foreach (var i in properties)
                {
                    var binding = BindingOperations.GetBindingExpression(element, i);
                    if (binding == null)
                        continue;
                    yield return binding;
                }
            }


private static readonly ConcurrentDictionary<Type, DependencyProperty[]> DependencyProperties = new ConcurrentDictionary<Type, DependencyProperty[]>();
    public static DependencyProperty[] GetDependencyProperties(this Type type)
            {
                return DependencyProperties.GetOrAdd(type, t =>
                {
                    var properties = GetDependencyProperties(TypeDescriptor.GetProperties(type, new Attribute[] { new PropertyFilterAttribute(PropertyFilterOptions.All) }));
                    return properties.ToArray();
                });
            }

            private static IEnumerable<DependencyProperty> GetDependencyProperties(PropertyDescriptorCollection collection)
            {
                if (collection == null)
                    yield break;
                foreach (PropertyDescriptor i in collection)
                {
                    var dpd = DependencyPropertyDescriptor.FromProperty(i);
                    if (dpd == null)
                        continue;
                    yield return dpd.DependencyProperty;
                }
            }

暫無
暫無

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

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