Finally after bit of digging code in PresentationFramework assembly
using reflector
, i was able to find out the exact RCA for this issue. As mentioned by nit, this behaviour is valid for all DP's and for all Controls not just DataGrid.
This is all to do with Dependency Property Value Precedence that
which value is given precedence over other while setting DP's.
(Enum is BaseValueSourceInternal which store this precedence order of DP's in WindowsBase.dll assembly)
DependencyObject class
contains method UpdateEffectiveValue
which is responsible for setting final actual value on any DP by calling SetValue
method on DataGrid instance.
UpdateEffectiveValue
method contains lot of logic before actually calling SetValue
method on DP.
Interesting check which is stopping from setting it via ControlTemplate is this one (It checks if new value precedence order is higher than old value precedence order only in that case value will be set on DP otherwise return without setting DP) -
if ((newEntry.BaseValueSourceInternal != BaseValueSourceInternal.Unknown)
&& (newEntry.BaseValueSourceInternal < oldEntry.BaseValueSourceInternal))
{
return (UpdateResult) 0;
}
In first case where dataGrid is direct child of window, DP property is set by these steps -
- WPF engine read BAML(compiled XAML) from top to bottom and once it encounter DataGrid, creates an instance of it.
- From constructor when we set selection mode DP,
SetValueCommon
method of DependencyObject
class gets called. It pass on old value and new value to method UpdateEffectiveValue
.
- Now,
old value's BaseValueSourceInternal is Unkown
and new value's BaseValueSourceInternal is set as Local
by SetValueCommon method. Hence, it gets passed from the if check mentioned above and DP gets set.
- Now, after creating a DataGrid instance, all attributes associated with DataGrid are read from BAML one by one and SetValueCommon method is called on every DP encountered.
- Since, SetValueCommon method's set
new value with BaseValueSourceInternal.Local
and old value is already BaseValueSourceInternal.Local
. So precedence order is same, that's why Single value gets set on DP.
In second case where DataGrid is placed inside ControlTemplate -
- DataGrid is not created when WPF engine is reading BAML since it is contained inside a ContentControl. It will be created only when ContentControl is rendered on GUI.
Framework's ApplyTemplate gets called which calls LoadContent method to load the template
.
- LoadContent internally calls much more methods which at last create an instance of DataGrid and set DP like in previous case which set current value set with BaseValueSourceInternal.Local.
- Now, once dataGrid instance is created
ApplyTemplatedParentValue
method gets called which tries to set all the DP's found on it by calling method UpdateEffectiveValue
.
Current value set on DP is set with BaseValueSourceInternal.Local
but new value
which it tries to set is set as BaseValueSourceInternal.ParentTemplate
.
- So, finally when it goes to method
UpdateEffectiveValue
, the if condition mentioned above fails since ParentTemplate precedence order is lower than Local
. Hence, SetValue never gets called on DP with new value which is a reason than when we comment the line from constructor it's working fine since old value's BaseValueSourceInternal is Unknown and new value's BaseValueSourceInternal is ParentTemplate.
As mentioned in the DP's precedence order that property set via animation holds more preference than Local value. So, ideally if we set the property in ControlTemplate via animation, it should work fine. I tried it out and it works completely fine -
<ControlTemplate x:Key="MyTemplate">
<local:ExtendedDataGrid ItemsSource="{Binding Collection,
RelativeSource={RelativeSource
Mode=FindAncestor, AncestorType=Window}}">
<local:ExtendedDataGrid.Columns>
<DataGridTextColumn Binding="{Binding}"/>
</local:ExtendedDataGrid.Columns>
<local:ExtendedDataGrid.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetProperty="SelectionMode">
<DiscreteObjectKeyFrame KeyTime="00:00:00"
Value="{x:Static DataGridSelectionMode.Single}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</local:ExtendedDataGrid.Triggers>
</local:ExtendedDataGrid>
</ControlTemplate>