【问题标题】:How do I set the binding of a child proxy relative to the DataContext?如何设置子代理相对于 DataContext 的绑定?
【发布时间】:2021-09-05 08:14:04
【问题描述】:

我看了很多话题,但都与UI元素的DataContext的定义有关。

我有一个任务需要完全不同的方法。 而且无论我对这个决定有多么困惑,我什么都想不出来。

问题描述。
最初,有一个简单的代理:

using System;
using System.Windows;


namespace Proxy
{
    /// <summary> Provides a <see cref="DependencyObject"/> proxy with
    /// one property and an event notifying about its change. </summary>
    public class Proxy : Freezable
    {
        /// <summary> Property for setting external bindings. </summary>
        public object Value
        {
            get { return (object)GetValue(ValueProperty); }
            set { SetValue(ValueProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Value.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ValueProperty =
            DependencyProperty.Register(nameof(Value), typeof(object), typeof(Proxy), new PropertyMetadata(null));

        protected override Freezable CreateInstanceCore()
        {
            throw new NotImplementedException();
        }
    }
}

如果你在任何元素的Resources中设置它,那么它可以通过一个简单的Binding得到DataContext:

<FrameworkElement.Resources>
    <proxy:ProxyValue x:Key="proxy"
                      Value="{Binding}"/>
</FrameworkElement.Resources>

同样,任何没有明确指定源的 Bindig 都将使用其资源中将代理实例声明为源的元素的 DataContext。

子代理注入。
现在,对于某个任务(它的条件与问题无关,所以我不会描述它)我需要一个嵌套(子)代理,它也可以分配一个相对于数据上下文的绑定。
我需要在代码中设置这个绑定。

一个高度简化的演示示例:

using System.Windows.Data;

namespace Proxy
{
    public class PregnantProxy : Proxy
    {
        public Proxy Child { get; } = new Proxy();

        public PregnantProxy()
        {
            Binding binding = new Binding();
            BindingOperations.SetBinding(this, ValueProperty, binding);
            BindingOperations.SetBinding(Child, ValueProperty, binding);
        }
    }
}
<StackPanel DataContext="Some data">
    <FrameworkElement.Resources>
        <proxy:PregnantProxy x:Key="proxy"/>
    </FrameworkElement.Resources>
    <TextBlock Text="{Binding}" Margin="10"/>
    <TextBlock Text="{Binding Value, Source={StaticResource proxy}}" Margin="10"/>
    <TextBlock Text="{Binding Child.Value, Source={StaticResource proxy}}" Margin="10"/>
</StackPanel>

父代理绑定按预期工作。
但是链接一个孩子不会返回任何东西。

如何为孩子设置正确的绑定?

【问题讨论】:

    标签: wpf xaml binding freezable


    【解决方案1】:

    目前,我已经实现了一个可行的解决方案,即找到父 FrameworkElement 并将子代理添加到父 FrameworkElement 的资源中。

    问题中显示的测试用例工作正常。

    using System;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    
    namespace Proxy
    {
        public class PregnantProxy : Proxy
        {
            public Proxy Child { get; } = new Proxy();
    
            public PregnantProxy()
            {
                BindingOperations.SetBinding(this, ParentProperty, FindAncestorFrameworkElement);
                Binding binding = new Binding() { RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(StackPanel), 1) };
                BindingOperations.SetBinding(this, ValueProperty, binding);
                BindingOperations.SetBinding(Child, ValueProperty, binding);
    
            }
    
    
            /// <summary>
            /// Родительский FrameworkElement
            /// </summary>
            public FrameworkElement Parent
            {
                get { return (FrameworkElement)GetValue(ParentProperty); }
                set { SetValue(ParentProperty, value); }
            }
    
            /// <summary><see cref="DependencyProperty"/> для свойства <see cref="Parent"/>.</summary>
            public static readonly DependencyProperty ParentProperty =
                DependencyProperty.Register(nameof(Parent), typeof(FrameworkElement), typeof(PregnantProxy), new PropertyMetadata(null, ParentChanged));
    
            private static void ParentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                PregnantProxy proxy = (PregnantProxy)d;
                if (e.OldValue is FrameworkElement oldElement)
                {
                    oldElement.Resources.Remove(proxy.key);
                }
                if (e.NewValue is FrameworkElement newElement)
                {
                    double key;
                    do
                    {
                        key = random.NextDouble();
                    } while (newElement.Resources.Contains(key));
                    newElement.Resources.Add(proxy.key = key, proxy.Child);
                }
    
                if (!Equals(BindingOperations.GetBinding(proxy, ParentProperty), FindAncestorFrameworkElement))
                    BindingOperations.SetBinding(proxy, ParentProperty, FindAncestorFrameworkElement);
    
            }
            private double key;
            private static readonly Random random = new Random();
            private static readonly Binding FindAncestorFrameworkElement = new Binding() { RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(FrameworkElement), 1) };
        }
    }
    

    但是如果资源被锁定或只读,这样的解决方案可能会抛出异常。

    最终解决方案。

    @Rekshino 的回答促使我朝着不同的方向寻找解决方案。
    我创建了一个扩展方法来设置 DependencyObject 相对于另一个 DependencyObject 的上下文。

    该方法可以应用于任何 DependecyObject。
    但是上下文相关的绑定只能由 Freezable 解释。 因此,对于 DependecyObject 的其余部分而言,它几乎没有意义。
    但也许我错过了一些东西,我可以以某种方式使用它或修改它。

    using System;
    using System.Linq;
    using System.Reflection;
    using System.Windows;
    
    namespace Proxy
    {
        public static class ProxyExtensionMethods
        {
            private static readonly Func<DependencyObject, DependencyObject, DependencyProperty, bool> ProvideSelfAsInheritanceContextHandler;
    
            static ProxyExtensionMethods()
            {
                var methods = typeof(DependencyObject)
                     .GetMethods(BindingFlags.Instance | BindingFlags.NonPublic);
    
                MethodInfo method = null;
    
                foreach (var meth in methods
                    .Where(m => m.Name == "ProvideSelfAsInheritanceContext" &&
                            m.ReturnType == typeof(bool)))
                {
                    var parameters = meth.GetParameters();
    
                    if (parameters?.Length == 2 &&
                        typeof(DependencyObject) == parameters[0].ParameterType &&
                        typeof(DependencyProperty) == parameters[1].ParameterType)
                    {
                        method = meth;
                        break;
                    }
                }
    
                ProvideSelfAsInheritanceContextHandler = (Func<DependencyObject, DependencyObject, DependencyProperty, bool>)
                     method
                     .CreateDelegate
                     (
                         typeof(Func<DependencyObject, DependencyObject, DependencyProperty, bool>)
                     );
    
            }
    
            /// <summary>Sets the DependecyObject context</summary>
            /// <param name="obj">The object for which the Context is to be set.</param>
            /// <param name="context">The object to be used as the Context.</param>
            public static void SetContext(this DependencyObject obj, DependencyObject context)
            {
                    ProvideSelfAsInheritanceContextHandler(context, obj, PrivateKey.DependencyProperty);
            }
    
            private static readonly DependencyPropertyKey PrivateKey=
                DependencyProperty.RegisterAttachedReadOnly("Private", typeof(object), typeof(ProxyExtensionMethods), new PropertyMetadata(null));
        }
    }
    

    用法示例。

    using System.Windows.Data;
    
    namespace Proxy
    {
        public class PregnantProxy : Proxy
        {
            public Proxy Child { get; } = new Proxy();
    
            public PregnantProxy()
            {
                Child.SetContext(this);
    
                Binding binding = new Binding() { };
                BindingOperations.SetBinding(this, ValueProperty, binding);
                BindingOperations.SetBinding(Child, ValueProperty, binding);
            }
        }
    }
    

    问题中显示的 XAML 可在此类实现中正常工作。

    如果有人在代码上有 cmets 并且这样的实现可能存在问题,我准备好仔细听了。

    【讨论】:

      【解决方案2】:

      “如果你将它设置在任何元素的Resources中,那么它可以通过简单的Binding得到DataContext”——这是关键错误。资源字典没有 DataContext 继承。你可以很容易地看到它,如果你添加到资源字典中,例如Label 并尝试对其使用绑定(参见下面的示例)。

      它适用于Text="{Binding Value, Source={StaticResource proxy}}" 依赖于从Freezable 类的继承,如果我没记错Freezable.ContextList,它会找出数据上下文并使用它,这是私有的,请参阅Freezable 的实现。此实现不适用于 Child,因为它不在资源字典中。

      因此,如果您不是从 Freezable 继承,而是从父类 DependencyObject 继承,那么 Text="{Binding Value, Source={StaticResource proxy}}" 也将不起作用。

      我不知道你需要这个构造做什么,它看起来有点奇怪,但是如果你从 FrameworkElement 继承并为代理提供一个 DataContext 并且它是子元素(在 XAML 中你可以对其进行硬编码,或使用StaticResource 或自定义MarkupExtension)它可以工作。查看修改后的代码。

          public class Proxy : FrameworkElement
          {
              /// <summary> Property for setting external bindings. </summary>
              public object Value
              {
                  get { return (object)GetValue(ValueProperty); }
                  set { SetValue(ValueProperty, value); }
              }
          
              // Using a DependencyProperty as the backing store for Value.  This enables animation, styling, binding, etc...
              public static readonly DependencyProperty ValueProperty =
                  DependencyProperty.Register(nameof(Value), typeof(object), typeof(Proxy), new PropertyMetadata(null)); 
      
              //protected override Freezable CreateInstanceCore()
              //{
              //    throw new NotImplementedException();
              //}
          
          }
          public class PregnantProxy : Proxy
          {
              public Proxy Child { get; } = new Proxy();
          
              public PregnantProxy()
              {
                  var binding = new Binding() {};
                  BindingOperations.SetBinding(this, ValueProperty, binding);
          
                  //Child
                  this.AddLogicalChild(Child);
                  BindingOperations.SetBinding(Child, DataContextProperty, binding);
          
                  BindingOperations.SetBinding(Child, ValueProperty, binding);
              }
          }
      

      并相应地在 XAML 中:

      <StackPanel DataContext="Some data">
          <StackPanel.Resources>
              <local:PregnantProxy x:Key="proxyResBinding"  DataContext="{Binding}"/>
              <local:PregnantProxy x:Key="proxyHardCodedDC"  DataContext="Proxy hardcoded DC"/>
      
              <Label x:Key="lblResBinding" DataContext="{Binding}"/>
              <Label x:Key="lblHardcoded" DataContext="hard coded DC"/>
          </StackPanel.Resources>
      
          <Label Content="{Binding}" Background="Yellow" />
      
          <Label Content="{Binding Child.Value, Source={StaticResource proxyResBinding}}" Background="Red"/>
          <Label Content="{Binding Value, Source={StaticResource proxyResBinding}}" Background="Red"/>
                  
          <Label Content="{Binding Child.Value, Source={StaticResource proxyHardCodedDC}}" Background="Green"/>
          <Label Content="{Binding Value, Source={StaticResource proxyHardCodedDC}}" Background="Green"/>
      
          <Label Content="{Binding DataContext, Source={StaticResource lblResBinding}}" Background="Red"/>
          <Label Content="{Binding DataContext, Source={StaticResource lblHardcoded}}" Background="Green"/>
                  
      </StackPanel>
      

      【讨论】:

      • 感谢您的关注和回答。我已经标记了答案,但不是解决方案。你自己测试过吗?我完全复制了您的代码而没有更改任何内容,但是来自 Proxy 和 Label 的 DataContext="{Binding}" 绑定不起作用。也许您需要注意代码性能的更多细微差别?我有一个 «WPF Framework» 项目类型。
      • DataContext="{Binding}" 被枚举来证明,Resource 字典没有 DataContext 继承,Resources 中的 {Binding} 只能与 StaticResources Source 等一起使用。
      • 好的!我误解了答案的意思。现在你已经澄清了你的答案,我明白了。我知道,为了获得默认的 DataContext,Resources 中的项目必须派生自 Freezable。将资源添加到代理也没有任何意义。要正常工作,需要实现internal interface IHaveResources
      • 您可以为资源条目设置DataContext,例如在“行为”中,但它看起来太矫枉过正了。
      猜你喜欢
      • 1970-01-01
      • 2016-04-04
      • 2023-03-21
      • 2010-09-27
      • 2013-02-19
      • 1970-01-01
      • 1970-01-01
      • 2012-08-21
      • 1970-01-01
      相关资源
      最近更新 更多