如何设置子代理相对于数据上下文的绑定?



我看了很多主题,但它们都以某种方式与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 中设置它,那么它可以通过一个简单的绑定来获取 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>

父代理绑定按预期工作。
但是链接子项不会返回任何内容。

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

"如果你在任何元素的资源中设置它,那么它可以通过一个简单的绑定来获取 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>

现在,我已经实现了一个工作解决方案,通过查找父框架元素并将子代理添加到父框架元素的资源中。

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

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的回答促使我从不同的方向寻找解决方案.
我创建了一个扩展方法来设置相对于另一个依赖对象的上下文。

该方法可以应用于任何 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 可与此类实现一起正常工作。

如果有人对代码有意见,并且这种实现可能存在问题,我准备仔细倾听。

最新更新