为什么DataTemplate.LoadContent()不尊重模板定义的触发器?

时间:2013-09-06 04:00:50

标签: c# wpf xaml datatemplate

TL-DR版本:

我们试图弄清楚触发器生效的DataTemplate的自动应用与触发器无效的手动调用DataTemplate.LoadContent()之间的区别。

现在详情......

但首先,我首先要说的是,这个问题是为了帮助我们理解框架及其在内部所做的工作,因此,相关代码严格来证明问题本身并且< em> not 以任何方式表示我们的实际代码。正如他们所说,它仅用于说明目的。 (只是试图避免不可避免的'我不明白你要做什么'或'那不是我会怎么做'的回应。再一次,它只是为了支持这个问题。希望这是有道理的。)

那就是说,考虑这个XAML定义一个String的DataTemplate,它有两个触发器(每个触发器都针对不同的元素)......

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" />

...按预期工作。在代码中,我们可以像这样访问扩展模板(Border)的根元素...

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中的Border和TextBlock(此处称为SomeOtherPresenter),而dataTemplateToUse.Triggers 显示两个已定义,但它们不起作用!< / p>

我正试图找出

  • a)为什么不,
  • b)如何启用/应用它们。

当然,“作弊”只是简单地启动一个新的ContentPresenter,设置其内容,然后将其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中定义的触发器(假设名称匹配并考虑名称范围等等?) >

1 个答案:

答案 0 :(得分:4)

我研究了这个的内部实现,并试图解释这里正在做什么框架。因此,我们知道ContentPresenter具有ContentTemplate属性。因此,每当我们将DataTemplate赋给ContentTemplate属性时,我们都可以看到它包含DataTemplate中定义的所有内容,包括触发器和所有内容。

现在,FrameworkElement有一个名为TemplateInternal的虚拟属性。派生的FrameworkElement类实现此属性。只要在FrameworkElement上应用默认模板,就会在内部填充此属性。

应用模板FrameworkElement检查是否填充了ContentTemplate,然后应用此模板的内容,否则应用内部属性i中的内容,e TemplateInternal

现在,Framework元素具有自我捕获PropertyChanges的保护方法,在验证属性更改后,触发应用于元素的datatemplate触发器。这意味着触发器不会被复制到control.Triggers但仍然保留在元素的Datatemplate中。 Framework元素使用内部StyleHelper类通过检查源和目标名称以及更改的属性来触发触发器。

因此,如果我们想通过元素访问它,则无法访问在框架元素上的默认模板上应用的触发器。我们可以从资源中加载该模板,如其他答案中所述。

现在,在第二种情况下,您通过DataTemplate方法将ContentPresenter内容应用到LoadContent()内容中,它只是创建了datatemplate的rootelement实例并更新了可视树它。它不会使用DataTemplate更新ContentTemplateTemplateInternal属性,因此不知道任何触发器。