3

TL-DR 版本:

我们试图弄清楚在触发器有效的情况下自动应用 DataTemplate 与在触发器无效的情况下手动调用 DataTemplate.LoadContent() 之间有什么区别。

现在详细...

但首先,让我先说这个问题是为了帮助我们理解框架以及它在内部做了什么,因此,相关的代码只是为了演示问题本身,并不代表我们的实际代码。正如他们所说,这仅用于说明目的。(只是试图避免不可避免的“我不明白你在做什么”或“我不会这样做”的回答。同样,这只是为了支持这个问题。希望这是有道理的。)

也就是说,考虑这个 XAML 定义一个带有两个触发器的字符串的 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" />

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

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,设置其内容,然后将其 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 中定义的触发器(提供匹配的名称并考虑名称范围等?)

4

1 回答 1

4

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

现在,FrameworkElement有一个名为TemplateInternal. 派生的 FrameworkElement 类实现此属性。每当在 FrameworkElement 上应用默认模板时,都会在内部填充此属性。

应用模板FrameworkElement时检查是否ContentTemplate已填充,然后应用此模板的内容,否则应用来自内部属性的内容,即TemplateInternal

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

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

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

于 2013-09-06T04:40:54.713 回答