0

我查看了很多主题,但它们都以某种方式与 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>

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

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

4

2 回答 2

0

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

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

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

我不知道你需要这个构造什么,它对我来说有点奇怪,但是如果你从代理继承FrameworkElement并为它提供一个子元素(在 XAML 中你可以硬编码它,或者使用或自定义它)它可以工作。请参阅修改后的代码。DataContextStaticResourceMarkupExtension

    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>
于 2021-06-22T08:41:35.050 回答
-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 与此类实现一起正常工作。

如果有人对代码和这种实现可能存在的问题有意见,我愿意仔细聆听。

于 2021-06-22T03:49:21.170 回答