Mark A. Donohoe
Mark A. Donohoe

Reputation: 30368

How can you set a DynamicResource in code-behind if the target is not a FrameworkElement?

Consider this BindingProxy class which is a subclass of Freezable (so it participates in resource-hierarchy lookups when added to a FrameworkElement's Resources collection)...

public class BindingProxy : Freezable {

    public BindingProxy(){}
    public BindingProxy(object value)
        => Value = value;

    protected override Freezable CreateInstanceCore()
        => new BindingProxy();

    #region Value Property

        public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
            nameof(Value),
            typeof(object),
            typeof(BindingProxy),
            new FrameworkPropertyMetadata(default));

        public object Value {
            get => GetValue(ValueProperty);
            set => SetValue(ValueProperty, value);
        }

    #endregion Value Property
}

You add it to your XAML like so...

<Window.Resources>
    <is:BindingProxy x:Key="TestValueProxy" Value="{DynamicResource TestValue}" />
</Window.Resources>

As you can see, Value is set to a DynamicResource and it will thus automatically track changes to a resource defined by that key, as expected.

Now if you wanted to set up a DynamicResource in code-behind instead of XAML, if the target object is a FrameworkElement, you would simply call SetResourceReference on it, like so...

myTextBlock.SetResourceReference(TextBlock.TextProperty, "MyTextResource")

However, SetResourceReference is only available on FrameworkElement objects, not Freezables so you can't use that on BindingProxy.

Digging into the source code for FrameworkElement.SetResourceReference, you find this...

public void SetResourceReference(DependencyProperty dp, object name){
    base.SetValue(dp, new ResourceReferenceExpression(name));
    HasResourceReference = true;
}

Unfortunately, ResourceReferenceExpression--the 'meat' of how this works--is internal so I can't get to that either.

So in code-behind, how can I set a DynamicResource on my Freezable-based object to mirror what I can do in XAML?

Upvotes: 1

Views: 1335

Answers (2)

taquion
taquion

Reputation: 2767

You can use a DynamicResourceExtension instance in your code:

var proxy = new BindingProxy();
var dynamicResourceExtension = new DynamicResourceExtension("TestValue");
proxy.Value = dynamicResourceExtension.ProvideValue(null);

If you see the code reference here you will see that ProvideValue returns a ResourceReferenceExpression when serviceProvider is null. Just almost the same thing that SetResourceReference does

Upvotes: 2

mm8
mm8

Reputation: 169200

Create a ResourceReferenceExpression using reflection:

Type type = typeof(System.Windows.Window).Assembly.GetType("System.Windows.ResourceReferenceExpression");
ConstructorInfo ctor = type.GetConstructors()[0];
object resourceReferenceExpression = ctor.Invoke(new object[] { "TestValue" });
TestValueProxy.SetValue(BindingProxy.ValueProperty, resourceReferenceExpression);

Obviously this code may break if the internal type changes, but there is not much else you can do if you really need to be able to apply a DynamicResource to a value of a Freezable dynamically.

Upvotes: 0

Related Questions