未在控件模板中为datagrid设置依赖项属性

时间:2013-10-24 18:36:32

标签: c# wpf xaml datagrid wpfdatagrid

我有一些要求自定义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设置为   扩展。我知道这是默认值,并在删除它之后   对于这两种情况都可以正常工作(也有很多方法可以设置默认值)但我想知道为什么它在一种情况下工作而不在其他情况下工作。

3 个答案:

答案 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 assemblyreflector中挖掘代码后,我能够找到此问题的确切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属性由这些步骤设置 -

  1. WPF引擎从上到下读取BAML(已编译的XAML),一旦遇到DataGrid,就会创建它的实例。
  2. 从我们设置选择模式DP时的构造函数中调用SetValueCommon类的DependencyObject方法。它将旧值和新值传递给方法UpdateEffectiveValue
  3. 现在,通过SetValueCommon方法old value's BaseValueSourceInternal is Unkownnew value's BaseValueSourceInternal is set as Local。因此,它从上面提到的if检查传递并且DP被设置。
  4. 现在,在创建DataGrid实例之后,将逐个从BAML中读取与DataGrid关联的所有属性,并在遇到的每个DP上调用SetValueCommon方法。
  5. 因为,SetValueCommon方法设置了new value with BaseValueSourceInternal.Localold value is already BaseValueSourceInternal.Local。所以优先顺序是相同的,这就是在DP上设置单值的原因。

  6. 在第二种情况下,DataGrid放在ControlTemplate中 -

    1. 当WPF引擎正在读取BAML时,不会创建DataGrid,因为它包含在ContentControl中。只有在GUI上呈现ContentControl时才会创建它。 Framework's ApplyTemplate gets called which calls LoadContent method to load the template
    2. LoadContent在内部调用了更多的方法,这些方法最后创建了一个DataGrid实例并设置了DP,就像之前的情况一样,它设置了使用BaseValueSourceInternal.Local设置的当前值。
    3. 现在,一旦创建了dataGrid实例,就会调用ApplyTemplatedParentValue方法,通过调用方法UpdateEffectiveValue来尝试设置在其上找到的所有DP。
    4. Current value set on DP is set with BaseValueSourceInternal.Local但它尝试设置的new value设置为BaseValueSourceInternal.ParentTemplate
    5. 所以,最后当它转到方法UpdateEffectiveValue时,上面提到的if条件因ParentTemplate precedence order is lower than Local而失败。因此,SetValue永远不会在DP上使用新值调用,这是一个原因,当我们从构造函数中注释该行时,它工作正常,因为旧值的BaseValueSourceInternal是Unknown而新值的BaseValueSourceInternal是ParentTemplate。

    6. 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>