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

前端 未结 5 1946
再見小時候
再見小時候 2021-01-19 00:22

I have a OneWayToSource binding that is not behaving as I expected when I set the DataContext of the target control. The property of the source is being set to default inste

相关标签:
5条回答
  • 2021-01-19 00:54

    You need 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));
                }
            });
    

    And set binding in XAML:

    extendends:FrameworkElementExtended.OneWaySourceRaise="{Binding}"
    

    where {Binding} - is binding to DataContext. You need:

        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;
                    }
                }
    
    0 讨论(0)
  • 2021-01-19 00:55

    Its a bug or perhabs not. Microsoft claims its by design. You first type x and then you kill DataContext by clicking on Button hence why the TextBox holds x and your viewModel.Text property gets newly initialized (its empty). When on datacontext changed getter will still be called. In the end you have no chance to fix this.

    You can however use two way and let it be.

    0 讨论(0)
  • 2021-01-19 00:57

    Here you will have to UpdateSource like below:

     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();
       }
    
    0 讨论(0)
  • 2021-01-19 01:00

    TextBox has a Binding in it's TextProperty and when you set TextBox's DataContext, TextBox will update it's source (viewmodel.Text) , no matter which type of the UpdateSourceTrigger.

    It's said that the first output in viewmodel

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

    is not triggered by UpdateSourceTrigger=PropertyChanged.

    It's just a process of 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;
        }
    }
    

    Because it's not triggered by UpdateSourceTrigger=PropertyChanged, the viewmodel will not know the value of TextBox.Text.

    When you type "Y",the trigger of PropertyChanged will working,so the viewmodel read text of TextBox.

    0 讨论(0)
  • 2021-01-19 01:14

    There is a bug in .NET 4 with one way to source bindings that it calls getter for OneWayToSource bindings thats why you are having this problem.You can verify it by putting breakpoint on tb.DataContext = _vm; you will find setter is called and just after that getter is called on Text property.You can resolve your problem by manually feeding the viewmodel values from view before assigning the datacontext..NET 4.5 resolves this issue. see here and here too

    private void Button1_Click(object sender, RoutedEventArgs e)
    {
       Debug.Print("'Set DataContext' button clicked");       
        _vm.Text=tb.Text;
        tb.DataContext = _vm;
    }
    
    0 讨论(0)
提交回复
热议问题