WPF DataTemplate resets some dependency properties when unloaded
Asked Answered
F

1

7

I have a DataTemplate consisting of a media element control that is derived from MediaElementBase from the WPF Media Kit library. The MediaElementBase class provides the two properties, LoadedBehavior and UnloadedBehavior that allow the user to specify what happens when the element is loaded/unloaded.

I am finding that when using this in a DataTemplate (as follows), these properties get reset to their default value when the template is unloaded, but before the Unloaded event is called, meaning only the default UnloadedBehavior will ever execute:

<DataTemplate DataType="{x:Type Channels:AnalogChannel}">
    <Controls:AnalogTvGraphFileElement
        LoadedBehavior="Play"
        UnloadedBehavior="Stop"
        Channel="{Binding}" />
</DataTemplate>

This doesn't occur when the control is simply an element on a page and Unloaded occurs through a normal navigate-away event.

Debugging the DependencyPropertyChanged EventHandler reveals that an internal method System.Windows.StyleHelper.InvalidatePropertiesOnTemplateNode (in PresentationFramework.dll) checks if a DependencyProperty is potentially inherited, and if it isn't, invalidates it. Sure enough, changing the property metadata for the LoadedBehavior / UnloadedBehavior to add FrameworkPropertyMetadataOptions.Inherits stops this property from being reset when the template changes.

Does anyone know why this occurs? I can add the Inherits flag as a workaround, as this element has no child elements that would be affected by this, but I'd like to know why / whether it is the right thing to do.

If you're after more information about what I'm doing and why I'm changing DataTemplates you can view this question for a description.

Finnish answered 24/11, 2010 at 6:51 Comment(3)
Which version of the WPF Media Kit are you using? The current binary release or are you compiling from source? At the time of writing this comment, the current binary (v1.5) is about a year behind the source tree. Jez fixed some bugs relating to properties values changing suddenly. I don't think the fix was directly related to the properties you're using, but to repro the problem it'd be useful to know which version you're using.Midshipmite
I am compiling from source. The issue is not specific to WPF Media Kit - I am able to reproduce in a simple stand-alone app.Finnish
The problem as described is about the UnloadedBehavior. I understand that the root cause - the fact that properties get reset on an unload - would be universal, but what problems does that cause in general? In the WPF Media Kit it's a problem because you've got this property where you only care what the value is after unloading. How often does that matter?Midshipmite
M
7

Rather than putting this element directly into the template, have you tried creating a user control that contains your AnalogTvGraphFileElement element, and then using that user control in your template?

If the problem here is being caused by the template systems going and unsetting properties that it set in the first place, moving your element into a user control should help, because the properties would no longer be being set from the template.

As for why you're seeing the behaviour in the first place, as far as I can tell the relative ordering of the Unloaded event and the loss of properties set via the template is not documented, so you shouldn't depend on any particular ordering. The fact that you will lose the property values is documented. (Or at least, it's implicit from the docs.) The WPF property system treats local values in a template as being a different sort of thing than normal local values outside of a template. See this MSDN page on dependency property precedence - 4b indicates that local property sets in the template are not the same thing as local properties. (It seems odd to make a distinction, but it should be possible to set property values from both sources by setting them in the template - type 4b - and then at runtime, going and finding the element in a particular instance of the template and setting its local value from code - type 3. And for that scenario you really would want type 3 local values to have higher precedence than type 4b local values.)

Weird as that seems, it might make more sense when you consider that a single template may be providing values for multiple instances. You've got just one local property setter that could affect any number of elements. (This means that a simple mental model of a template as being a factory that builds a visual tree and sets properties on that tree would be wrong. It's a factory that builds a visual tree, but it doesn't set properties. The property system simply ensures that in the absence of any higher-precedence property value sources, the elements in a visual tree that's the template for something will pick up values from setters in the template.)

That page does just about tell you that type 4b properties will disappear once the template ceases to be active - the template is no longer the template for the templated parent, and so any local values provided by that template no longer qualify as candidate values of type 4b (or any other type for that matter) for the property. In short, once the template ceases to be the template for something, it ceases to have any business providing values for that thing or anything in it. This implies that the visual tree for an instance of a template enters a weird limbo state in which it is no longer the template for anything but hasn't yet unloaded.

Of course, it doesn't seem useful for the template to stop providing values before the relevant visual tree has finished unloading, but perhaps properties whose value is only significant when an element unloads are a bit of a weird case, and not one that was specifically designed for. Thinking about it, they probably shouldn't ever be dependency properties - almost all the features that make DPs useful don't really mean much once you're unloaded from the visual tree. So arguably, it's a bug in the Media Kit that UnloadedBehaviour is a DP in the first place.

It's inconsistent that inherited properties (type 10) shut down later than local template property sets (type 4b), but then I'm not sure it's reasonable to expect any particular order here. It's not obvious that the documentation implies one order or the other, so either order is correct...and WPF appears to make use of that by picking one order in one scenario and the other in another scenario.

Midshipmite answered 29/11, 2010 at 9:1 Comment(6)
This stops the behaviour described in the question so long as the UserControl doesn't have any DependencyProperties used to pass values through to the AnalogTvGraphFileElement. Essentially, it just creates another layer and doesn't really explain why non-inheritable properties are reset when inheritable properties aren't.Finnish
I was hoping to get you unstuck speedily, and was waiting to find out whether it was a media kit version issue before digging in. I would guess that the reason that properties are reset is because properties set using local property syntax are considered to be a distinct source, and when the template goes away, those property values go away. The fact that inherited ones stay sounds like a bug...Midshipmite
OK, I've edited that answer to add some information about how the property system can come to be doing this in the first place. This probably doesn't help much - the upshot is "don't do that". But it does suggest that you really do need an extra layer such as a user control. Try using regular properties instead of DPs. Or failing that, either snapshot the property value in your UserControl's Loaded event, or use the DependencyPropertyHelper to discover where the value's coming from, and ignore it when it reverts back to a low-predence source.Midshipmite
+1 for the great explanation. It makes a lot more sense now and I'll definitely have a closer look at the document you linked. The 'don't do that' upshot isn't entirely unexpected, nor is it that disappointing. I was more curious if there was a particular reason why. Also, having a DP value only useful at Unload does seem a bit strange if I step back and view it in that context. Wrapping in a UserControl may indeed be a better solution, although I am thinking that relying on the unloaded behaviour at all is a flawed design.Finnish
In regards to your comment on the question, the default UnloadedBehavior is Close, which frees resources (such as the graph). I was hoping to use Stop instead so that the resources are not freed when changing from Analog to Digital TV, and the Graph is simply stopped so that restarting would be faster. This turned out not to be the case anyway - stopping and starting the graph took about the same time as freeing and recreating it. I will leave this question unanswered for the remainder of the bounty to see if anyone else weighs in.Finnish
Interestingly, I've just noticed that the native WPF MediaElement control has the exact same Loaded/UnloadedBehavior properties, and mimics the same issue when used in a DataTemplate...Finnish

© 2022 - 2024 — McMap. All rights reserved.