I have some requirement to customize datagrid, so i create my own dataGrid extending WPF datagrid. Small relevant code posted below -
public class ExtendedDataGrid : DataGrid
{
public ExtendedDataGrid()
{
this.SelectionMode = DataGridSelectionMode.Extended;
}
}
I create its instance in a window and set SelectionMode
to Single
which works perfectly fine and property gets set to Single
for the dataGrid. So far all good.
But in case i place my DataGrid in ControlTemplate, SelectionMode
never gets set to Single
. SelectionMode is just for example no DP is getting set via XAML if i explicitly set that value in constructor of DataGrid.
Small sample replicating the problem is here -
<Grid>
<Grid.Resources>
<ControlTemplate x:Key="MyTemplate">
<local:ExtendedDataGrid ItemsSource="{Binding Collection,
RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType=Window}}"
SelectionMode="Single">
<local:ExtendedDataGrid.Columns>
<DataGridTextColumn Binding="{Binding}"/>
</local:ExtendedDataGrid.Columns>
</local:ExtendedDataGrid>
</ControlTemplate>
</Grid.Resources>
<ContentControl Template="{StaticResource MyTemplate}"/>
<local:ExtendedDataGrid ItemsSource="{Binding Collection,
RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType=Window}}"
Grid.Row="1" SelectionMode="Single">
<local:ExtendedDataGrid.Columns>
<DataGridTextColumn Binding="{Binding}"/>
</local:ExtendedDataGrid.Columns>
</local:ExtendedDataGrid>
</Grid>
For second DataGrid its working fine but not working for DataGrid placed inside ControlTemplate. Why this weird behaviour? Is it some bug in DataGrid code?
Note - It will work fine if i comment the line from DataGrid constructor where i am explicitly setting SelectionMode to Extended. I know that's default value and after removing that it will work fine for both cases (also there are many ways to set default value) but i want to know why it works in one case and not in other.
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 -
SetValueCommon
method of DependencyObject
class gets called. It pass on old value and new value to method UpdateEffectiveValue
.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.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 -
Framework's ApplyTemplate gets called which calls LoadContent method to load the template
.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
.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>