user1211286
user1211286

Reputation: 765

How to stop WPF Property Value Inheritance propagation at certain boundaries

I have an attached property that specifies the "inherits" option to achieve WPF property value inheritance. I can see that the property value is propagated across the visual tree. However, with large visual trees, this may impact performance quite a bit.

I would therefore like the attached property value inheritance of my attached property to stop at certain boundaries, more specifically instances of a particular class.

I have read about FrameworkElement.InheritanceBehavior, which a control can set to something like SkipAllNext, which stops property value inheritance (for all properties, though), but also affects resource lookup. The effect on resource lookup is not desirable.

Is there any other way to control the propagation, either in the attached property or in the class that should act as a boundary?

What I am trying to achieve is here: WPF container to turn all child controls to read-only. The solution with value inheritance to have all controls in a form turn to read-only based on a global switch is pretty good. It just has the performance penalty as mentioned there and here.

Upvotes: 2

Views: 1610

Answers (3)

Glenn Slayden
Glenn Slayden

Reputation: 18839

The explanation for this is a bit sad and complex, but fortunately the solution is very easy. You do not need to use AddOwner(...).

To block inheritance when using DependencyProperty.OverrideMetadata(...), you cannot use the FrameworkPropertyMetadata constructor to specify the FrameworkPropertyMetadataOptions flag "OverridesInheritanceBehavior". The reason is that, for certain FPMO flags, the default FrameworkPropertyMetadata.Merge(...) function only processes flags that have been explicitly modified via the FPM property setters.

The Inherits flag/property is another one that also has this behavior, and also you need to clear it from any base metadata. So the only way to have Merge(...) clear the value that results (after merging with the base property metadata) is to, again, explicitly set it via its FPM setter.

It's confusing and unfortunately a very unnatural/unexpected/non-obvious design, so here is an example. Assume SomeDependencyProperty is a bool property, and the base metadata specifies a DefaultValue of false, and it has DP inheritance enabled. You want to override the default value to be true on your derived class MyType.

So the whole issue discussed on this page/question is that the following doesn't work:

SomeDependencyProperty.OverrideMetadata(typeof(MyType), 
    new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.OverridesInheritanceBehavior));

As discussed above, there are two problems with the preceding, but the more obvious one is that, even if the FPMO.OverridesInheritanceBehavior works as expected, you haven't un-set the FPMO.Inherits flag itself. Obviously, there's no way to unset a bit flag by ORing together bit-flags that can only be asserted. So essentially, after the Merge, you would be overriding the inheritance behavior--but with inheritance still being enabled (due to Merge). And finally, with inheritance still ultimately enabled, your desired DefaultValue will essentially have no effect.

Solution

Instead, use the property setters, as shown in the following, which works to block inheritance and, thus, allow your modified DefaultValue to take effect:

SomeDependencyProperty.OverrideMetadata(typeof(MyType),
    new FrameworkPropertyMetadata(true)
    {
        Inherits = false,
        OverridesInheritanceBehavior = true,
    });

reference: Merge(...) @ FrameworkPropertyMetadata.cs

Upvotes: 0

Sheridan
Sheridan

Reputation: 69985

There is one possibility that I know about. It's not really what you're after, but you can use the DependencyProperty.OverrideMetadata Method to override the PropertyMetadata of the DependencyProperty in an extended control:

public class SomeControl : OriginalControl
{
    static SomeControl()
    {
        OriginalControl.SomeProperty.OverrideMetadata(typeof(SomeControl), 
            new FrameworkPropertyMetadata(defaultValue, 
            FrameworkPropertyMetadataOptions.OverridesInheritanceBehavior));
    }
}

Apart from this, (I could be wrong but) I don't think that you'll be able to achieve your goal... WPF just wasn't designed like that.

Upvotes: 0

nmclean
nmclean

Reputation: 7734

AddOwner seems to work:

class BoundaryElement : FrameworkElement {
    public static readonly DependencyProperty CustomProperty =
        AttachedProperties.CustomProperty.AddOwner(typeof(BoundaryElement),
            new FrameworkPropertyMetadata() {Inherits = false});
}

I tried setting Inherits = false through OverrideMetadata, but this will only affect the BoundaryElement itself, and the attached property value continues to propagate to its children. AddOwner effectively replaces the property at the BoundaryElement so that the original doesn't exist to inherit from.

Upvotes: 1

Related Questions