TL-DR version:
We're trying to figure out what the difference is between the automatic application of a DataTemplate where triggers are in effect vs. manually calling DataTemplate.LoadContent() where triggers are not in effect.
Now the details...
But first, let me start by saying this question is to help us understand the framework and what it's doing internally, and as such, the associated code is strictly to demonstrate the question itself and does not represent our actual code in any way. It is, as they say, for illustrative purposes only. (Just trying to avoid the inevitable 'I don't understand what' you're trying to do' or the 'That's not how I would do it' responses. Again, it's just to support the question. Hope that makes sense.)
That said, consider this XAML defining a String's DataTemplate with two triggers (each targeting a different element)...
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>
Then in another location in XAML where that template is in scope, we have this...
<ContentPresenter x:Name="TestPresenter" Content="This is a Test" />
...which works as expected. In code, we can gain access to the root element of the expanded template (the Border) like this...
var expandedTemplateRootElement = VisualTreeHelper.GetChild(TestPresenter, 0) as FrameworkElement;
...but how and where are the triggers applied? They obviously work, but both expandedTemplateRootElement.Triggers.Count and TestPresenter.Triggers.Count return zero.
As stated in the question's title itself, if we try to expand out the content from the DataTemplate manually, like this...
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;
...while this does properly show the Border and the TextBlock in the second ContentPresenter (called SomeOtherPresenter here), and dataTemplateToUse.Triggers does show two are defined, they don't work!
I'm trying to find out
- a) Why not, and
- b) How can they be enabled/applied.
Of course the 'cheat' would be to simply spin up a new ContentPresenter, set its Content, then set its ContentTemplate to the DataTemplate in question. Then you could just stuff the entire thing in another ContentPresenter and let the framework worry about the details, like this...
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;
...but that still doesn't explain how the triggers are actually applied to the expanded contents themselves when automatically expanded vs. manual expansion.
This entire post asked a completely different way... Is it possible to programmatically create triggers on FrameworkElements, mimicking those defined in a DataTemplate (provided the names matched up and taking into consideration name scopes, etc.?)
I looked into the internal implementation of this and will try to explain what framework is doing here. So we know that
ContentPresenter
hasContentTemplate
property with it. So whenever we assign theDataTemplate
toContentTemplate
property we can see that it contains everything defined in the DataTemplate including Triggers and everything.Now,
FrameworkElement
has one virtual property calledTemplateInternal
. Derived FrameworkElement classes implement this property. Whenever default template is applied on FrameworkElement this property is populated internally.While applying the template
FrameworkElement
checks ifContentTemplate
is populated then apply the content of this template else apply the content from internal property i,eTemplateInternal
Now Framework element has protected method for capturing PropertyChanges in itself which after verifying the property change, triggers the datatemplate Triggers applied on the element. So that means the triggers are not copied to control.Triggers but still remain with Datatemplate of the element. Framework element uses internal
StyleHelper
class to fire the trigger by checking the source and target name and changed property.So, accessing the triggers applied on default template on framework element is not possible if we want to access it through the element. We can load that template from the resources as explained in other answers also.
Now, in second case where you are applying the
DataTemplate
content into theContentPresenter
content byLoadContent()
method it just creates the instance of the rootelement of datatemplate and update the visual tree with it. It does not update theContentTemplate
orTemplateInternal
property with the DataTemplate hence does not know about any triggers.