我有一些要求自定义datagrid,所以我创建了自己的dataGrid扩展WPF数据网格。下面张贴的小相关代码 -
public class ExtendedDataGrid : DataGrid
{
public ExtendedDataGrid()
{
this.SelectionMode = DataGridSelectionMode.Extended;
}
}
我在一个窗口中创建了它的实例,并将SelectionMode
设置为Single
,其工作完全正常,并且dataGrid的属性设置为Single
。到目前为止一切都很好。
但是如果我将我的DataGrid放在ControlTemplate中,SelectionMode
永远不会设置为Single
。 SelectionMode只是例如,如果我在DataGrid的构造函数中显式设置该值,则不会通过XAML设置DP。
复制问题的小样本就在这里 -
<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>
对于第二个DataGrid,它的工作正常但不适用于放置在ControlTemplate内的DataGrid。 为什么这种奇怪的行为?是DataGrid代码中的一些错误吗?
注意 - 如果我对DataGrid中的行进行注释,它将正常工作 构造函数,我明确地将SelectionMode设置为 扩展。我知道这是默认值,并在删除它之后 对于这两种情况都可以正常工作(也有很多方法可以设置默认值)但我想知道为什么它在一种情况下工作而不在其他情况下工作。
答案 0 :(得分:2)
这是一个很好的问题,要回答它需要了解WPF引擎如何创建这两个DataGrid
实例。
对于DataGrid
的直接子项Window
的第一个实例,在InitializeComponents()
的构造函数调用Window
时创建该实例。我不会深入了解InitializeComponents
如何工作,但只是一个jist它调用方法System.Windows.Application.LoadComponent()
和LoadComponent()
加载位于传递的XAML
文件URI
,并将其转换为XAML
文件的根元素指定的对象的实例。在执行此操作时,它首先调用要创建的元素的默认构造函数,然后再次设置属性中提到的DependancyProperties
。
现在,您放在ControlTemplate
内的第二个实例。将在元素上应用ControlTemplate
时创建该实例。如果您未应用Template
,则永远不会创建实例。在应用Template
时,会调用ControlTemplate.LoadContent()
来创建ControlTemplate
的根元素。现在LoadContent()
采用不同的粗略来创建UIElements
中定义的controlTemplate
。它确实为每个元素调用默认构造函数,但是当设置DependancyProperties
时,它会运行多个检查来确定属性值。简而言之,它检查是否已为元素实例上的特定DependancyProperty
设置了任何值(即该值不是默认值,它是{{1的ValueDictionary中实例的本地值条目)它不考虑xaml中指定的值。因此,在这种情况下DependancyObject
调用DataGrid的默认构造函数时,我们设置LoadComponents
值。在加载内容时,SelectionModeProperty
会检查它并返回相同的值并忽略xaml中指定的值。
所有控件都适用ControlTemplate
。
答案 1 :(得分:1)
对不起,但是我无法回答你的问题,为什么在使用ControlTemplate
时它没有用,但我可以给你一个更好的设置方法扩展类中继承属性的默认值,可以解决您的问题。
可以使用DependencyProperty
方法为继承的DependencyProperty.OverrideMetadata
提供具有默认值的新元数据。您可以使用SelectionMode
构造函数为static
属性设置自己的默认值,如下所示:
static ExtendedDataGrid()
{
SelectionModeProperty.OverrideMetadata(typeof(ExtendedDataGrid),
new FrameworkPropertyMetadata(2));
}
更新&gt;&gt;&gt;
如果使用表示所需值的整数替换SelectionMode
枚举,代码将编译。我只使用了SelectionMode.Extended
值(现在替换为整数值 - 2)因为你在你的示例中使用了
我建议使用这种设置默认值的替代方法,因为如果您在构造函数中注释掉SelectionMode
属性的默认值为{{1}的行,则表示您的问题会消失}。我认为,如果你用它来替换那条线,那么你的问题可能会消失。
答案 2 :(得分:1)
最后,在使用PresentationFramework assembly
在reflector
中挖掘代码后,我能够找到此问题的确切RCA。如nit所述,此行为对所有DP和所有控件都有效,而不仅仅是DataGrid。
这与Dependency Property Value Precedence有关 在设置DP时,哪个值优先于其他值。 (Enum是BaseValueSourceInternal,它在WindowsBase.dll程序集中存储DP的优先顺序)
DependencyObject class
包含方法UpdateEffectiveValue
,它负责通过调用DataGrid实例上的SetValue
方法在任何DP上设置最终实际值。
UpdateEffectiveValue
方法在DP上实际调用SetValue
方法之前包含许多逻辑。
通过ControlTemplate停止设置的有趣检查就是这个(它检查新值优先顺序是否高于旧值优先顺序只有在那种情况下,值将在DP上设置,否则返回而不设置DP < / strong>) -
if ((newEntry.BaseValueSourceInternal != BaseValueSourceInternal.Unknown)
&& (newEntry.BaseValueSourceInternal < oldEntry.BaseValueSourceInternal))
{
return (UpdateResult) 0;
}
在第一种情况下,dataGrid是窗口的直接子节点,DP属性由这些步骤设置 -
SetValueCommon
类的DependencyObject
方法。它将旧值和新值传递给方法UpdateEffectiveValue
。old value's BaseValueSourceInternal is Unkown
和new value's BaseValueSourceInternal is set as Local
。因此,它从上面提到的if检查传递并且DP被设置。new value with BaseValueSourceInternal.Local
和old value is already BaseValueSourceInternal.Local
。所以优先顺序是相同的,这就是在DP上设置单值的原因。在第二种情况下,DataGrid放在ControlTemplate中 -
Framework's ApplyTemplate gets called which calls LoadContent method to load the template
。ApplyTemplatedParentValue
方法,通过调用方法UpdateEffectiveValue
来尝试设置在其上找到的所有DP。Current value set on DP is set with BaseValueSourceInternal.Local
但它尝试设置的new value
设置为BaseValueSourceInternal.ParentTemplate
。UpdateEffectiveValue
时,上面提到的if条件因ParentTemplate precedence order is lower than Local
而失败。因此,SetValue永远不会在DP上使用新值调用,这是一个原因,当我们从构造函数中注释该行时,它工作正常,因为旧值的BaseValueSourceInternal是Unknown而新值的BaseValueSourceInternal是ParentTemplate。如DP's precedence order中所述,通过动画设置的属性比Local值保留更多的首选项。所以,理想情况下,如果我们通过动画在ControlTemplate中设置属性,它应该可以正常工作。我尝试了它,它完全正常 -
<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>