C# WPF binding OneWayToSource将源属性设置为"&引用;当DataContext发生更改时

C# WPF binding OneWayToSource将源属性设置为"&引用;当DataContext发生更改时,c#,wpf,xaml,binding,C#,Wpf,Xaml,Binding,我有一个OneWayToSource绑定,当我设置目标控件的DataContext时,它的行为与我预期的不一样。正在将源控件的属性设置为默认值,而不是目标控件的属性值 我在标准WPF窗口中创建了一个非常简单的程序,它说明了我的问题: XAML <StackPanel> <TextBox x:Name="tb" Text="{Binding Path=Text,Mode=OneWayToSource,UpdateSourceTrigger=PropertyChange

我有一个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>
ViewModel.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);
   }
}
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;
      }
   }
}
公共类视图模型
{
私有字符串_文本;
公共字符串文本
{
获取{return\u Text;}
设置
{
调试。打印(
“ViewModel.Text(旧值=”+(_Text??)+
,新值=“+(值??)+”);
_文本=值;
}
}
}
文本框
tb
以空DataContext开始,因此绑定不需要做任何事情。因此,如果我在文本框中键入一些内容,比如“X”,ViewModel.text属性将保持为空

如果我随后单击设置数据上下文按钮,我希望
ViewModel.Text
属性设置为
TextBox.Text
属性的“X”。相反,它被设置为“”。当然,绑定工作正常,因为如果我在文本框中的“X”后面键入“Y”,它会将
ViewModel.text
属性设置为“XY”

下面是一个输出示例(由于计算顺序的原因,最后两行是违反直觉的,但它们肯定都在键入“Y”后立即出现):

文本框更改为X
单击“设置数据上下文”按钮
Text(旧值=,新值=)
ViewModel.Text(旧值=,新值=XY)
文本框更改为XY

为什么在设置DataContext时,
ViewModel.Text
属性被设置为“”而不是“X”

我做错了什么?我错过什么了吗?我对装订有什么误解吗

编辑:我希望输出为:

文本框更改为X
单击“设置数据上下文”按钮
ViewModel.Text(旧值=,新值=X
ViewModel.Text(旧值=X,新值=XY)
文本框更改为XY


TextBox在其TextProperty中有一个绑定,当您设置TextBox的DataContext时,TextBox将更新其源(viewmodel.Text),无论是哪种类型的UpdateSourceTrigger

据说viewmodel中的第一个输出

ViewModel.Text(旧值=,新值=)

不是由
UpdateSourceTrigger=PropertyChanged
触发的

这只是一个初始化过程:

private string _Text;
public string Text
{
    get { return _Text; }
    set
    {
        Debug.Print(
           "ViewModel.Text (old value=" + (_Text ?? "<null>") +
           ", new value=" + (value ?? "<null>") + ")");
        _Text = value;
    }
}
私有字符串\u文本;
公共字符串文本
{
获取{return\u Text;}
设置
{
调试。打印(
“ViewModel.Text(旧值=“+(\u Text??”)+
,新值=“+(值??)+”);
_文本=值;
}
}
因为它不是由
UpdateSourceTrigger=PropertyChanged
触发的,所以viewmodel将不知道TextBox.Text的值


当您键入“Y”时,PropertyChanged的触发器将起作用,因此viewmodel将读取TextBox的文本。

在此,您必须
更新资源,如下所示:

 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();
   }

这是虫子还是虫子。微软声称它是精心设计的。首先键入x,然后通过单击按钮终止DataContext,这就是为什么TextBox保存x和viewModel.Text属性得到新初始化(其为空)。在datacontext上更改时,仍将调用getter。最终你没有机会解决这个问题


但是,您可以使用两种方式,任其发展。

在.NET 4中有一个错误,它使用一种方式来源绑定,它为OneWayToSource绑定调用getter,这就是您遇到此问题的原因。您可以通过在tb.DataContext=\u vm;您会发现setter被调用,紧接着getter在Text属性上被调用。您可以通过在分配datacontext之前从视图中手动输入viewmodel值来解决问题。NET 4.5解决了此问题。 及


您需要
附加属性

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;
                }
            }
公共静态IEnumerable GetBindings(此T元素,Func Func=null),其中T:DependencyObject { var properties=element.GetType().GetDependencyProperties(); foreach(属性中的var i) { var binding=BindingOperations.GetBindingExpression(元素,i); if(binding==null) 继续; 退换货装订; } } 私有静态只读ConcurrentDictionary DependencyProperties=new ConcurrentDictionary(); 公共静态DependencyProperty[]GetDependencyProperties(此类型) { 返回dependencProperties.GetOrAdd(类型,t=> { var properties=GetDependencyProperties(TypeDescriptor.GetProperties(类型,新属性[]{new PropertyFilterAttribute(PropertyFilterOptions.All)}); 返回properties.ToArray(); }); } 私有静态IEnumerable GetDependencyProperties(PropertyDescriptorCollection集合) { if(集合==null) 屈服断裂; foreach(集合中的PropertyDescriptor i) { var dpd=DependencyPropertyDescriptor.FromProperty(i); if(dpd==null) 继续; 收益率-收益率依赖性属性; } }
这肯定会达到最终的效果,但似乎有点麻烦,必须为每个这样的绑定完成。此外,它会导致ViewModel属性设置两次(尽管我有
    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;
                }
            }