WPF DataTemplate在卸载时会重置某些依赖属性

7
我有一个包含媒体元素控件的,它是从WPF Media Kit库中派生的MediaElementBaseMediaElementBase类提供了两个属性:LoadedBehaviorUnloadedBehavior,允许用户指定元素加载/卸载时发生的情况。
我发现当在中使用时,这些属性会在模板卸载之前被重置为默认值,但在Unloaded事件被调用之前,这意味着只有默认的UnloadedBehavior会被执行:
<DataTemplate DataType="{x:Type Channels:AnalogChannel}">
    <Controls:AnalogTvGraphFileElement
        LoadedBehavior="Play"
        UnloadedBehavior="Stop"
        Channel="{Binding}" />
</DataTemplate>

当控件仅是页面上的一个元素时,并且通过正常的导航事件发生Unloaded,则不会发生这种情况。
调试DependencyPropertyChanged EventHandler揭示了一个内部方法System.Windows.StyleHelper.InvalidatePropertiesOnTemplateNode(在PresentationFramework.dll中),它检查DependencyProperty是否可能被继承,如果没有,则使其无效。确实,更改LoadedBehavior/UnloadedBehavior的属性元数据以添加FrameworkPropertyMetadataOptions.Inherits,可以防止在模板更改时重置此属性。
有人知道为什么会发生这种情况吗?我可以将Inherits标志添加为解决方法,因为此元素没有子元素会受到影响,但我想知道为什么/是否正确。
如果您需要更多关于我正在做什么以及为什么要更改DataTemplates的信息,请查看this question以获取描述。

你使用的是哪个版本的WPF Media Kit?当前的二进制发布版还是从源代码编译的?在撰写本评论时,当前的二进制发布版(v1.5)已经落后于源代码树约一年。Jez修复了一些与属性值突然变化有关的错误。我不认为这个修复与你正在使用的属性直接相关,但为了重现问题,知道你使用的版本将会很有用。 - Ian Griffiths
我正在从源代码编译。问题并不特定于WPF Media Kit - 我能够在一个简单的独立应用程序中重现它。 - jeffora
所描述的问题涉及到UnloadedBehavior。我理解根本原因——属性在卸载时被重置——是普遍存在的,但这会导致什么一般性问题呢?在WPF Media Kit中,这是一个问题,因为你只关心卸载后该属性的值是什么。这有多重要? - Ian Griffiths
1个回答

7
与其直接将此元素放入模板中,您是否尝试创建一个包含您的AnalogTvGraphFileElement元素的用户控件,然后在模板中使用该用户控件?
如果问题是由于模板系统去取消设置它首先设置的属性引起的,将您的元素移动到用户控件中应该有所帮助,因为属性将不再从模板中设置。
至于为什么您首先看到这种行为,据我所知,卸载事件和通过模板设置的属性丢失的相对顺序没有记录在文档中,因此您不应该依赖于任何特定的顺序。您将丢失属性值的事实是记录在文档中的(或者至少是从文档中推断出来的)。WPF属性系统将模板中的本地值视为与模板外部的普通本地值不同类型的内容。请参阅此MSDN页面上的依赖属性优先级 - 4b表示模板中的本地属性集不同于本地属性。 (似乎区分这一点很奇怪,但应该可以通过在模板中设置它们(类型4b)然后在运行时查找特定实例中的元素并从代码中设置其本地值(类型3)来同时从两个源设置属性值,并且对于该方案,您确实希望类型3本地值具有比类型4b本地值更高的优先级。)
虽然这似乎很奇怪,但是当您考虑到单个模板可能为多个实例提供值时,它可能会更有意义。您只有一个本地属性设置器,可以影响任意数量的元素。(这意味着将模板简化为构建可视树并设置该树上的属性的工厂的简单心理模型是错误的。它是构建可视树的工厂,但它不设置属性。属性系统仅确保在没有任何更高优先级的属性值源的情况下,作为某些东西的模板的元素将从模板中的设置器获取值。)
那个页面几乎告诉您,类型4b属性将在模板停止活动后消失 - 模板不再是模板化父级的模板,因此由该模板提供的任何本地值都不再符合属性的类型4b(或任何其他类型)的候选值。简而言之,一旦模板不再是某物的模板,它就不再为该物或其中任何内容提供值。这意味着模板实例的可视树进入了一种奇怪的悬空状态,在这种状态下,它不再是任何东西的模板,但尚未卸载。
当然,模板在相关的可视树卸载完成之前停止提供值似乎并不实用,但也许属性的值仅在元素卸载时才具有重要意义是一个有点奇怪的情况,并且不是特别设计的情况。想一想,它们可能永远不应该成为依赖属性 - 几乎所有使DP有用的功能一旦从可视树中卸载就没有多大意义了。因此,可以认为Media Kit中UnloadedBehaviour首先是一个DP是一个错误。
继承属性(类型10)关闭比本地模板属性集(类型4b)晚是不一致的,但我不确定在这里期望任何特定的顺序是否合理。文档并没有暗示一个顺序或另一个顺序,所以任何一种顺序都是正确的...而WPF似乎利用这一点,在一个场景中选择一个顺序,在另一个场景中选择另一个顺序。

只要UserControl没有使用任何DependencyProperties将值传递到AnalogTvGraphFileElement,就可以停止问题描述中的行为。本质上,它只是创建了另一层,并没有真正解释为什么非可继承属性在可继承属性不重置时会被重置。 - jeffora
我希望能够快速帮助您解决问题,并等待了解是否存在媒体套件版本问题,然后再深入挖掘。我猜测属性被重置的原因是使用本地属性语法设置的属性被认为是不同的来源,当模板消失时,这些属性值也会消失。继承的属性仍然存在似乎是一个错误... - Ian Griffiths
好的,我已经编辑了那个答案,添加了一些关于属性系统如何首先执行此操作的信息。这可能并没有太大帮助 - 结论是“不要这样做”。但它确实表明您确实需要额外的层次,例如用户控件。尝试使用常规属性而不是DP。或者如果失败,可以在UserControl的Loaded事件中拍摄属性值的快照,或者使用DependencyPropertyHelper发现值来自哪里,并在其回退到低优先级源时忽略它。 - Ian Griffiths
对这个很好的解释点一个赞。现在我理解多了,一定会仔细看你提供的文件链接。 “不要那样做”的结论并不是完全出乎意料,也不令人失望。我更加好奇的是是否有某个特殊的原因。此外,在Unload时只有DP值才有用,如果我退后一步以那种情况来看它,似乎有点奇怪。将其包装在UserControl中可能确实是更好的解决方案,尽管我认为在任何情况下依赖于卸载行为都是一个有缺陷的设计。 - jeffora
关于您在问题上的评论, 默认的UnloadedBehavior是Close,这会释放资源(例如图形)。我希望使用Stop而不是Close,这样在从模拟电视切换到数字电视时不会释放资源,而且图形只是停止,以便重新启动更快。但事实并非如此 - 停止和启动图形所需的时间与释放和重新创建它所需的时间大致相同。我将保留此问题未回答的奖励,以查看是否有其他人参与讨论。 - jeffora
有趣的是,我刚刚注意到本地的WPF MediaElement控件具有完全相同的Loaded/UnloadedBehavior属性,并且在DataTemplate中使用时会模仿相同的问题... - jeffora

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接