CodeGo.net>为什么DataTemplate.LoadContent()不尊重模板定义的触发器?
作者:互联网
TL-DR版本:
我们正在尝试找出有效的触发器在DataTemplate的自动应用程序与无效的触发器之间手动调用DataTemplate.LoadContent()的区别.
现在的细节…
但是首先,让我首先说这个问题是为了帮助我们了解框架及其内部功能,因此,关联的代码严格地用于演示问题本身,并且不以任何方式代表我们的实际代码.正如他们所说,仅用于说明目的. (只是试图避免不可避免的“我不明白您要做什么”或“那不是我要怎么做”的回答.同样,这只是为了支持这个问题.希望是有道理的.)
就是说,请考虑使用两个触发器定义每个字符串的DataTemplate的XAML(每个触发器针对不同的元素)…
xmlns:system="clr-namespace:System;assembly=mscorlib"
...
<DataTemplate DataType="{x:Type system:String}">
<Border x:Name="Bd" Background="Yellow">
<TextBlock x:Name="Tb" Text="{Binding StringFormat='Formatted Value: {0}'}" />
</Border>
<DataTemplate.Triggers>
<Trigger SourceName="Bd" Property="IsMouseOver" Value="True">
<Setter TargetName="Bd" Property="Background" Value="Red" />
</Trigger>
<Trigger SourceName="Tb" Property="IsMouseOver" Value="True">
<Setter TargetName="Tb" Property="Foreground" Value="Yellow" />
</Trigger>
</DataTemplate.Triggers>
</DataTemplate>
然后在XAML中该模板所在范围的另一个位置,我们有了这个…
<ContentPresenter x:Name="TestPresenter" Content="This is a Test" />
…如预期般运作.在代码中,我们可以像这样访问扩展模板的根元素(边框)…
var expandedTemplateRootElement = VisualTreeHelper.GetChild(TestPresenter, 0) as FrameworkElement;
…但是如何以及在哪里应用触发器?它们显然可以工作,但是expandTemplateRootElement.Triggers.Count和TestPresenter.Triggers.Count都返回零.
如问题标题本身所述,如果我们尝试手动从DataTemplate扩展内容,就像这样…
var rawContents = "Show me the money!";
var dataTemplateToUse = TestPresenter.FindResource(new DataTemplateKey(rawContents.GetType()));
var expandedTemplateRootElement = dataTemplateToUse.LoadContent() as FrameworkElement;
expandedTemplateRootElement.DataContext = rawContents;
SomeOtherPresenter.Contents = expandedTemplateRootElement;
…虽然这确实在第二个ContentPresenter(这里称为SomeOtherPresenter)中正确显示了Border和TextBlock,并且dataTemplateToUse.Triggers确实显示了两个已定义,但它们不起作用!
我正在尝试找出
> a)为什么不可以,以及
> b)如何启用/应用它们.
当然,“骗子”只是简单地启动一个新的ContentPresenter,设置其Content,然后将其ContentTemplate设置为有问题的DataTemplate.然后,您可以将整个内容填充到另一个ContentPresenter中,让框架担心细节,像这样……
var rawContents = "Hello World";
var dataTemplateToUse = TestPresenter.FindResource(new DataTemplateKey(rawContents.GetType())) as DataTemplate;
var innerPresenter = new ContentPresenter()
{
Content = rawContents,
ContentTemplate = dataTemplateToUse
};
YetAnotherPresenter.Content = innerPresenter;
…但是,这仍然无法解释在自动扩展与手动扩展之间,触发器如何实际应用于扩展内容本身.
整篇文章都提出了一种完全不同的方式…是否可以以编程方式在FrameworkElements上创建触发器,模仿在DataTemplate中定义的触发器(提供匹配的名称并考虑名称范围等)?
解决方法:
我研究了此方法的内部实现,并将尝试解释此框架在做什么.因此,我们知道ContentPresenter具有ContentTemplate属性.因此,每当将DataTemplate分配给ContentTemplate属性时,我们都可以看到它包含在DataTemplate中定义的所有内容,包括触发器和所有内容.
现在,FrameworkElement具有一个称为TemplateInternal的虚拟属性.派生的FrameworkElement类实现此属性.每当将默认模板应用于FrameworkElement时,都会在内部填充此属性.
在应用模板时,FrameworkElement将检查是否填充了ContentTemplate,然后应用此模板的内容,否则将应用来自内部属性的内容,即TemplateInternal
现在,Framework元素本身具有用于捕获PropertyChanges的受保护方法,该方法可以在验证属性更改后触发应用于元素的datatemplate触发器.因此这意味着触发器不会复制到control.Triggers,但仍保留在元素的Datatemplate中. Framework元素使用内部StyleHelper类通过检查源和目标名称以及已更改的属性来触发触发器.
因此,如果要通过元素访问默认触发器,则无法访问框架元素的默认模板上应用的触发器.我们也可以从资源中加载该模板,如其他答案中所述.
现在,在第二种情况下,您将通过LoadContent()方法将DataTemplate内容应用于ContentPresenter内容,它只是创建数据模板的rootelement的实例,并使用它来更新可视化树.它不会使用DataTemplate更新ContentTemplate或TemplateInternal属性,因此不知道任何触发器.
标签:datatemplate,wpf,xaml,c 来源: https://codeday.me/bug/20191030/1968028.html