65.9K
CodeProject 正在变化。 阅读更多。
Home

构建您自己的 Silverlight 数据网格:第三步

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.07/5 (6投票s)

2009年6月4日

CPOL

43分钟阅读

viewsIcon

46552

downloadIcon

2490

为数据网格添加标题。

1. 引言

教程概述

本文是关于如何使用Silverlight和GOA Toolkit构建功能齐全的数据网格的教程的第三部分。

在本教程的第一部分,我们介绍了创建只读网格主体的方法。在第二部分,我们重点介绍了编辑和验证功能的实现。在本第三部分,我们将重点关注网格的标题。

在本文中,我们将假设您已阅读完前两部分教程。如果尚未阅读,我们强烈建议您这样做。您可以在这里访问它们。

入门

本教程是使用GOA Toolkit 2009 Vol1 build 289编写的。如果您在本文章发布前已实现了教程的第一步和第二步,您可能需要升级。请确保您使用的是GOA Toolkit 2009 Vol1 build 289或更高版本。

请确保您已在计算机上安装了此版本或更高版本(www.netikatech.com/downloads)。

目标

在前两个教程中,我们构建了网格的主体部分。我们构建的网格主体具有数据网格的所有基本功能(单元格、导航、单元格编辑等)。已实现了一些高级功能(虚拟模式、DataTemplate等)。尽管如此,在我们能够将标题与显示的单元格关联起来之前,我们的数据网格将无法使用。

这就是第三个教程的目的。

由于我们的网格可以显示分层数据(节点和子节点),因此我们必须能够将标题与网格的任何级别关联起来。我们将以两种不同的方式实现这一点:

  • 允许在网格内部每个子集合的顶部显示标题,
  • 并允许在网格顶部显示标题。

在第一种情况下,网格的外观将如下所示:

Headers Inside The DataGrid

在第二种情况下,标题将显示在网格的顶部。

Headers Outside The DataGrid

我们也允许实现介于两者之间的所有替代方案:

  • 在网格主体内部显示部分标题,在外部显示部分标题。
  • 仅根据预定义的条件或规则显示顶部标题。
  • ...

当显示分层数据时,用户有时会觉得显示混乱。他们可能难以理解层次结构的各个级别。为了使分层数据更易读,我们将允许我们的网格在每个级别的顶部显示一个标题。如果您查看上面的两张图片,您可以看到在标题顶部显示的“国家”、“地区”和“人员”等标题。标题和标签并非必需。我们可以选择显示它们,也可以不显示。我们还可以选择仅显示标题而不显示标签,或反之亦然。

我们还将实现允许用户调整标题大小所需的功能。

超出范围

我们不讨论通过单击行标题来对网格数据进行排序的可能性。此功能更多地与数据相关,而不是与标题相关。添加一个在用户单击标题时显示向上箭头或向下箭头的视觉状态并不难。更困难的是将相应的排序规则应用于底层数据。这可能是另一个教程的主题。

2. 准备网格

重构网格的显示

我们的数据网格能够显示大量复杂的分层数据。我们必须确保数据以用户最易读的方式呈现。在网格主体中添加标题将增加复杂性。因此,在开始实现标题之前,让我们稍微改变一下行显示方式,使网格更具吸引力。

隔行变色

当我们将网格主体属性AlternateType设置为“Items”值时,每隔一行将以交替颜色显示背景。

Alternate Type

此功能旨在提高网格的可读性。然而,交替颜色过于鲜艳,使得网格难以阅读。

在GOA Toolkit的最新版本中,GOAOpen项目generic.xaml文件顶部的画笔和颜色定义已修改,以使交替颜色更加朴素。让我们以同样的方式修改我们的generic.xaml文件。

在GOAOpen项目generic.xaml文件顶部,选择颜色定义部分。这是文件中的“color”标签

<!--=======================================================================-->
<!--=======================================================================-->
<!--=============================  COLORS =================================-->
<!--=======================================================================-->
<!--=======================================================================-->

和“Standard Silverlight Controls”标签之间的部分。

<!--======================================================================-->
<!--======================================================================-->
<!--=============== STANDARD SILVERLIGHT CONTROLS ========================-->
<!--======================================================================-->
<!--======================================================================-->

让我们用以下部分替换此部分:

<!--======================================================================-->
<!--======================================================================-->
<!--=============================  COLORS ================================-->
<!--======================================================================-->
<!--======================================================================-->

<!-- Default -->
<!-- ======= -->
<SolidColorBrush x:Key="DefaultForeground" Color="#FF526168"/>
<Color x:Key="DefaultForegroundColor">#FF526168</Color>

<LinearGradientBrush x:Key="DefaultFocus" 
            EndPoint="1,1" StartPoint="0,0">
    <GradientStop Color="#FF262B2D" Offset="0"/>
    <GradientStop Color="#FF262B2D" Offset="1"/>
</LinearGradientBrush>

<SolidColorBrush x:Key="DefaultShadow" Color="#33272B2D"/>

<SolidColorBrush x:Key="DefaultDisabled" Color="#FFE8E9EA"/>

<SolidColorBrush x:Key="DefaultScrollBackground" Color="#FFD0E0E6"/>

<SolidColorBrush x:Key="DefaultControlBackground" Color="#FFF0F6F7"/>

<LinearGradientBrush x:Key="DefaultStroke" 
               EndPoint="1,1" StartPoint="0,0">
    <GradientStop Color="#FF8096A1" Offset="0"/>
    <GradientStop Color="#FF8096A1" Offset="1"/>
</LinearGradientBrush>

<SolidColorBrush x:Key="DefaultAlternativeBackground" Color="#FFE2ECF1"/>

<SolidColorBrush x:Key="DefaultListControlStroke" Color="#FF99B0BB" />

<LinearGradientBrush x:Key="DefaultBackground" 
           EndPoint="0.5,1" StartPoint="0.5,0">
    <GradientStop Color="#FF81A4BB" Offset="0"/>
    <GradientStop Color="#FFD9EBF7" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultBackgroundHorizontal" 
           EndPoint="0,0" StartPoint="1,0">
    <GradientStop Color="#FF81A4BB" Offset="0"/>
    <GradientStop Color="#FFD9EBF7" Offset="1"/>
</LinearGradientBrush>

<LinearGradientBrush x:Key="DefaultDownColor" 
           EndPoint="0.5,1" StartPoint="0.5,0">
    <GradientStop Color="#FFFF9700"/>
    <GradientStop Color="#FFFDCE28" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDownColorHorizontal" 
           EndPoint="0,0" StartPoint="1,0">
    <GradientStop Color="#FFFF9700"/>
    <GradientStop Color="#FFFDCE28" Offset="1"/>
</LinearGradientBrush>

<LinearGradientBrush x:Key="DefaultReflectVertical" 
           EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#CCFFFEED" Offset="0"/>
    <GradientStop Color="#33FFFFFF" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultReflectHorizontal" 
           EndPoint="1,0" StartPoint="0,0">
    <GradientStop Color="#CCFFFEED" Offset="0"/>
    <GradientStop Color="#33FFFFFF" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDarkGradientBottomVertical" 
           EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#00044164" Offset="0"/>
    <GradientStop Color="#4C044164" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDarkGradientBottomHorizontal" 
           EndPoint="1,0" StartPoint="0,0">
    <GradientStop Color="#00044164" Offset="0"/>
    <GradientStop Color="#4C044164" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultToolBarBackgroundBrush" 
           EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#FFA8C1D2" Offset="0.3"/>
    <GradientStop Color="#FFCAE0EE" Offset="1"/>
    <GradientStop Color="#FFB3CEE0" Offset="0"/>
</LinearGradientBrush>

<!-- Background:Blue, StandardColor: Blue, ActionColor: Orange -->
<!-- ========================================================= -->
<!--<SolidColorBrush x:Key="DefaultForeground" Color="#FF526168" />
<Color x:Key="DefaultForegroundColor">#FF526168</Color>
<SolidColorBrush x:Key="DefaultFocus" Color="#FF262B2D" />
<SolidColorBrush x:Key="DefaultShadow" Color="#33272B2D"/>
<SolidColorBrush x:Key="DefaultDisabled" Color="#FFE8E9EA"/>
<SolidColorBrush x:Key="DefaultScrollBackground" Color="#FFD0E0E6"/>
<SolidColorBrush x:Key="DefaultControlBackground" Color="#FFF0F6F7"/>
<SolidColorBrush x:Key="DefaultStroke" Color="#FF8096A1" />
<SolidColorBrush x:Key="DefaultAlternativeBackground" Color="#FFE2ECF1"/>
<SolidColorBrush x:Key="DefaultListControlStroke" Color="#FF99B0BB" />

<LinearGradientBrush x:Key="DefaultBackground" 
            EndPoint="0.5,1" StartPoint="0.5,0">
    <GradientStop Color="#FF81A4BB" Offset="0"/>
    <GradientStop Color="#FFD9EBF7" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultBackgroundHorizontal" 
            EndPoint="0,0" StartPoint="1,0">
    <GradientStop Color="#FF81A4BB" Offset="0"/>
    <GradientStop Color="#FFD9EBF7" Offset="1"/>
</LinearGradientBrush>

<LinearGradientBrush x:Key="DefaultDownColor" 
            EndPoint="0.5,1" StartPoint="0.5,0">
    <GradientStop Color="#FFFF9700"/>
    <GradientStop Color="#FFFDCE28" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDownColorHorizontal" 
            EndPoint="0,0" StartPoint="1,0">
    <GradientStop Color="#FFFF9700"/>
    <GradientStop Color="#FFFDCE28" Offset="1"/>
</LinearGradientBrush>

<LinearGradientBrush x:Key="DefaultReflectVertical" 
            EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#CCFFFEED" Offset="0"/>
    <GradientStop Color="#33FFFFFF" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultReflectHorizontal" 
            EndPoint="1,0" StartPoint="0,0">
    <GradientStop Color="#CCFFFEED" Offset="0"/>
    <GradientStop Color="#33FFFFFF" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDarkGradientBottomVertical" 
            EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#00044164" Offset="0"/>
    <GradientStop Color="#4C044164" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDarkGradientBottomHorizontal" 
            EndPoint="1,0" StartPoint="0,0">
    <GradientStop Color="#00044164" Offset="0"/>
    <GradientStop Color="#4C044164" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultToolBarBackgroundBrush" 
            EndPoint="0,1" StartPoint="0,0">
<GradientStop Color="#FFA8C1D2" Offset="0.3"/>
<GradientStop Color="#FFCAE0EE" Offset="1"/>
<GradientStop Color="#FFB3CEE0" Offset="0"/>
</LinearGradientBrush>-->

<!--Background:Beige, StandardColor: Brown, ActionColor: Green-->
<!--==========================================================-->
<!--<SolidColorBrush x:Key="DefaultForeground" Color="#FF4B4329" />
<Color x:Key="DefaultForegroundColor">#FF4B4329</Color>
<SolidColorBrush x:Key="DefaultFocus" Color="#FF35302A" />
<SolidColorBrush x:Key="DefaultShadow" Color="#33272B2D"/>
<SolidColorBrush x:Key="DefaultDisabled" Color="#FFD6CC9F"/>
<SolidColorBrush x:Key="DefaultScrollBackground" Color="#FFC8C1A3"/>
<SolidColorBrush x:Key="DefaultControlBackground" Color="#FFF4F0E2"/>
<SolidColorBrush x:Key="DefaultAlternativeBackground" Color="#FFE7E2C1"/>
<SolidColorBrush x:Key="DefaultListControlStroke" Color="#FFC4BDA8" />
<SolidColorBrush x:Key="DefaultStroke" Color="#FFA79F87" />

<LinearGradientBrush x:Key="DefaultBackground" 
            EndPoint="0.5,1" StartPoint="0.5,0">
    <GradientStop Color="#FFDAD2B1"/>
    <GradientStop Color="#FFE4DBAF" Offset="1"/>
</LinearGradientBrush>
    <LinearGradientBrush x:Key="DefaultBackgroundHorizontal" 
            EndPoint="0,0" StartPoint="1,0">
    <GradientStop Color="#FFDAD2B1"/>
    <GradientStop Color="#FFE4DBAF" Offset="1"/>
</LinearGradientBrush>

<LinearGradientBrush x:Key="DefaultDownColor" 
            EndPoint="0.5,1" StartPoint="0.5,0">
    <GradientStop Color="#FF375900"/>
    <GradientStop Color="#FFA9E42B" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDownColorHorizontal" 
            EndPoint="0,0" StartPoint="1,0">
    <GradientStop Color="#FF375900"/>
    <GradientStop Color="#FFA9E42B" Offset="1"/>
</LinearGradientBrush>

<LinearGradientBrush x:Key="DefaultReflectVertical" 
            EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#CCF7E9B9" Offset="0"/>
    <GradientStop Color="#4CF4EFD7" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultReflectHorizontal" 
            EndPoint="1,0" StartPoint="0,0">
    <GradientStop Color="#CCF7E9B9" Offset="0"/>
    <GradientStop Color="#4CF4EFD7" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDarkGradientBottomVertical" 
            EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#00213800" Offset="0"/>
    <GradientStop Color="#33213800" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDarkGradientBottomHorizontal" 
            EndPoint="1,0" StartPoint="0,0">
    <GradientStop Color="#00213800" Offset="0"/>
    <GradientStop Color="#33213800" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultToolBarBackgroundBrush" 
            EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#FFDAD2B1"/>
    <GradientStop Color="#FFE4DBAF" Offset="1"/>
</LinearGradientBrush>-->


<!--All Grey-->
<!--========-->
<!--<SolidColorBrush x:Key="DefaultForeground" Color="#FF2D2D2D" />
<Color x:Key="DefaultForegroundColor">#FF2D2D2D</Color>
<SolidColorBrush x:Key="DefaultFocus" Color="#FF5E5E5E" />
<SolidColorBrush x:Key="DefaultShadow" Color="#00272B2D"/>
<SolidColorBrush x:Key="DefaultDisabled" Color="#FFE8E9EA"/>
<SolidColorBrush x:Key="DefaultScrollBackground" Color="#FFECECEC"/>
<SolidColorBrush x:Key="DefaultControlBackground" Color="#FFF7F7F7"/>
<SolidColorBrush x:Key="DefaultStroke" Color="#FF888888" />
<SolidColorBrush x:Key="DefaultAlternativeBackground" Color="#FFEAEAEA"/>
<SolidColorBrush x:Key="DefaultListControlStroke" Color="#FFAFAFAF" />

<LinearGradientBrush x:Key="DefaultBackground" 
               EndPoint="0.5,1" StartPoint="0.5,0">
    <GradientStop Color="#FFD4D4D4" Offset="1"/>
    <GradientStop Color="#FFB1B1B1" Offset="0"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultBackgroundHorizontal" 
               EndPoint="0,0" StartPoint="1,0">
    <GradientStop Color="#FFD4D4D4" Offset="1"/>
    <GradientStop Color="#FFB1B1B1" Offset="0"/>
</LinearGradientBrush>

<LinearGradientBrush x:Key="DefaultDownColor" 
               EndPoint="0.5,1" StartPoint="0.5,0">
    <GradientStop Color="#FF8F8F8F" Offset="0"/>
    <GradientStop Color="#FF9B9B9B" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDownColorHorizontal" 
               EndPoint="0,0" StartPoint="1,0">
    <GradientStop Color="#FF8F8F8F" Offset="0"/>
    <GradientStop Color="#FF9B9B9B" Offset="1"/>
</LinearGradientBrush>

<LinearGradientBrush x:Key="DefaultReflectVertical" 
               EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#CCFFFFFF"/>
    <GradientStop Color="#19FFFFFF" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultReflectHorizontal" 
               EndPoint="1,0" StartPoint="0,0">
    <GradientStop Color="#CCFFFFFF"/>
    <GradientStop Color="#19FFFFFF" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDarkGradientBottomVertical" 
               EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#66535353" Offset="1"/>
    <GradientStop Color="#00535353"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDarkGradientBottomHorizontal" 
               EndPoint="1,0" StartPoint="0,0">
    <GradientStop Color="#66535353" Offset="1"/>
    <GradientStop Color="#00535353"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultToolBarBackgroundBrush" 
               EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#FFE8E8E8"/>
    <GradientStop Color="#FFEAEAEA" Offset="1"/>
    <GradientStop Color="#FFB5B5B5" Offset="0.5"/>
</LinearGradientBrush>-->

<!--Black & White only no grey no color-->
<!--===================================-->
<!--<SolidColorBrush x:Key="DefaultForeground" Color="Black" />
<Color x:Key="DefaultForegroundColor">Black</Color>
<SolidColorBrush x:Key="DefaultFocus" Color="Black" />
<SolidColorBrush x:Key="DefaultShadow" Color="#00272B2D"/>
<SolidColorBrush x:Key="DefaultDisabled" Color="White"/>
<SolidColorBrush x:Key="DefaultScrollBackground" Color="White"/>
<SolidColorBrush x:Key="DefaultControlBackground" Color="White"/>
<SolidColorBrush x:Key="DefaultStroke" Color="Black" />
<SolidColorBrush x:Key="DefaultAlternativeBackground" Color="White"/>
<SolidColorBrush x:Key="DefaultListControlStroke" Color="Black" />

<SolidColorBrush x:Key="DefaultBackground" Color="White" />
<SolidColorBrush x:Key="DefaultBackgroundHorizontal" Color="White" />
<SolidColorBrush x:Key="DefaultReflectVertical" Color="#00FFFFFF" />
<SolidColorBrush x:Key="DefaultReflectHorizontal" Color="#00FFFFFF" />
<SolidColorBrush x:Key="DefaultToolBarBackgroundBrush" Color="White" />
    
<LinearGradientBrush x:Key="DefaultDownColor" 
              EndPoint="0.5,1" StartPoint="0.5,0">
    <GradientStop Color="#FF000000" Offset="1"/>
    <GradientStop Color="#FF000000" Offset="0.8501"/>
    <GradientStop Color="#00000000" Offset="0.85"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDownColorHorizontal" 
              EndPoint="0,0" StartPoint="1,0">
   <GradientStop Color="#FF000000" Offset="1"/>
    <GradientStop Color="#FF000000" Offset="0.8501"/>
    <GradientStop Color="#00000000" Offset="0.85"/>
</LinearGradientBrush>

<LinearGradientBrush x:Key="DefaultDarkGradientBottomVertical" 
              EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#FF000000" Offset="1"/>
    <GradientStop Color="#FF000000" Offset="0.8501"/>
    <GradientStop Color="#00000000" Offset="0.85"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDarkGradientBottomHorizontal" 
              EndPoint="1,0" StartPoint="0,0">
    <GradientStop Color="#FF000000" Offset="1"/>
    <GradientStop Color="#FF000000" Offset="0.8501"/>
    <GradientStop Color="#00000000" Offset="0.85"/>
</LinearGradientBrush>-->

<!--Black-->
<!--=====-->
<!--<SolidColorBrush x:Key="DefaultForeground" Color="#FFECECEC" />
<Color x:Key="DefaultForegroundColor">#FFECECEC</Color>
<SolidColorBrush x:Key="DefaultShadow" Color="Transparent"/>
<SolidColorBrush x:Key="DefaultDisabled" Color="#FF1A1A1A"/>
<SolidColorBrush x:Key="DefaultScrollBackground" Color="#FF454545"/>
<SolidColorBrush x:Key="DefaultControlBackground" Color="#FF3D3D3D"/>
<SolidColorBrush x:Key="DefaultStroke" Color="#FF373737" />
<SolidColorBrush x:Key="DefaultAlternativeBackground" Color="#FF626262"/>
<SolidColorBrush x:Key="DefaultListControlStroke" Color="#FF606060" />

<LinearGradientBrush x:Key="DefaultFocus" 
             EndPoint="0.5,1" StartPoint="0.5,0">
    <GradientStop Color="#FFFFFFFF"/>
    <GradientStop Color="#FFFFFFFF" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultBackground" 
             EndPoint="0.5,1" StartPoint="0.5,0">
    <GradientStop Color="#FF6C6C6C"/>
    <GradientStop Color="#FF373737" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultBackgroundHorizontal" 
             EndPoint="0,0" StartPoint="1,0">
    <GradientStop Color="#FF6C6C6C"/>
    <GradientStop Color="#FF373737" Offset="1"/>
</LinearGradientBrush>

<LinearGradientBrush x:Key="DefaultDownColor" 
             EndPoint="0.5,1" StartPoint="0.5,0">
    <GradientStop Color="#FFB90700"/>
    <GradientStop Color="#FFFF3A00" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDownColorHorizontal" 
             EndPoint="0,0" StartPoint="1,0">
    <GradientStop Color="#FFB90700"/>
    <GradientStop Color="#FFFF3A00" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultReflectVertical" 
             EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#7FFFFFFF" Offset="0"/>
    <GradientStop Color="#19FFFFFF" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultReflectHorizontal" 
             EndPoint="1,0" StartPoint="0,0">
    <GradientStop Color="#7FFFFFFF" Offset="0"/>
    <GradientStop Color="#19FFFFFF" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDarkGradientBottomVertical" 
             EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#00535353" Offset="0"/>
    <GradientStop Color="#4CF9F9F9" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDarkGradientBottomHorizontal" 
             EndPoint="1,0" StartPoint="0,0">
    <GradientStop Color="#00535353" Offset="0"/>
    <GradientStop Color="#4CF9F9F9" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultToolBarBackgroundBrush" 
             EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#FF494949" Offset="0.424"/>
    <GradientStop Color="#FF494949" Offset="1"/>
    <GradientStop Color="#FF7A7A7A" Offset="0"/>
</LinearGradientBrush>-->

<!--White & Ice blue-->
<!--================-->
<!--<SolidColorBrush x:Key="DefaultForeground" Color="#FF3F3F3F" />
<Color x:Key="DefaultForegroundColor">#FF3F3F3F</Color>
<SolidColorBrush x:Key="DefaultShadow" Color="Transparent"/>
<SolidColorBrush x:Key="DefaultDisabled" Color="#FFE4E4E4"/>
<SolidColorBrush x:Key="DefaultFocus" Color="#FF000000"/>
<SolidColorBrush x:Key="DefaultScrollBackground" Color="White"/>
<SolidColorBrush x:Key="DefaultControlBackground" Color="White"/>
<SolidColorBrush x:Key="DefaultStroke" Color="#FFA1A1A1" />
<SolidColorBrush x:Key="DefaultAlternativeBackground" Color="#FFF2F2F2"/>
<SolidColorBrush x:Key="DefaultListControlStroke" Color="#FFC6C6C6" />
    
<LinearGradientBrush x:Key="DefaultBackground" 
             EndPoint="0.5,1" StartPoint="0.5,0">
    <GradientStop Color="#FFEAEAEA"/>
    <GradientStop Color="#FFFFFFFF" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultBackgroundHorizontal" 
             EndPoint="0,0" StartPoint="1,0">
    <GradientStop Color="#FFEAEAEA"/>
    <GradientStop Color="#FFFFFFFF" Offset="1"/>
</LinearGradientBrush>

<LinearGradientBrush x:Key="DefaultDownColor" 
             EndPoint="0.5,1" StartPoint="0.5,0">
    <GradientStop Color="#FF2E99C1"/>
    <GradientStop Color="#FF5EC3F2" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDownColorHorizontal" 
             EndPoint="0,0" StartPoint="1,0">
    <GradientStop Color="#FF2E99C1"/>
    <GradientStop Color="#FF5EC3F2" Offset="1"/>
</LinearGradientBrush>

<LinearGradientBrush x:Key="DefaultReflectVertical" 
             EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#B2FFFFFF" Offset="0"/>
    <GradientStop Color="#4CFFFFFF" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultReflectHorizontal" 
             EndPoint="1,0" StartPoint="0,0">
    <GradientStop Color="#B2FFFFFF" Offset="0"/>
    <GradientStop Color="#4CFFFFFF" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDarkGradientBottomVertical" 
             EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#005E5E5E" Offset="0"/>
    <GradientStop Color="#4C5C5C5C" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDarkGradientBottomHorizontal" 
             EndPoint="1,0" StartPoint="0,0">
    <GradientStop Color="#005E5E5E" Offset="0"/>
    <GradientStop Color="#4C5C5C5C" Offset="1"/>
</LinearGradientBrush>
<SolidColorBrush x:Key="DefaultToolBarBackgroundBrush" Color="White" />-->

<!--Grey Blue, Vista colors-->
<!--=======================-->
<!--<SolidColorBrush x:Key="DefaultForeground" Color="Black" />
<Color x:Key="DefaultForegroundColor">Black</Color>
<SolidColorBrush x:Key="DefaultShadow" Color="Transparent"/>
<SolidColorBrush x:Key="DefaultDisabled" Color="#FFF4F4F4"/>
<SolidColorBrush x:Key="DefaultFocus" Color="#FF000000"/>
<SolidColorBrush x:Key="DefaultScrollBackground" Color="White"/>
<SolidColorBrush x:Key="DefaultControlBackground" Color="White"/>
<SolidColorBrush x:Key="DefaultStroke" Color="#FF707070" />
<SolidColorBrush x:Key="DefaultAlternativeBackground" Color="#FFF2F2F2"/>
<SolidColorBrush x:Key="DefaultListControlStroke" Color="#FFC6C6C6" />

<LinearGradientBrush x:Key="DefaultBackground" 
             EndPoint="0.5,1" StartPoint="0.5,0">
    <GradientStop Color="#FFCFCFCF"/>
    <GradientStop Color="#FFDDDDDD" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultBackgroundHorizontal" 
             EndPoint="0,0" StartPoint="1,0">
    <GradientStop Color="#FFCFCFCF"/>
    <GradientStop Color="#FFDDDDDD" Offset="1"/>
</LinearGradientBrush>

<LinearGradientBrush x:Key="DefaultDownColor" 
              EndPoint="0.5,1" StartPoint="0.5,0">
    <GradientStop Color="#FF68B3DB"/>
    <GradientStop Color="#FF98D1EF" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDownColorHorizontal" 
              EndPoint="0,0" StartPoint="1,0">
    <GradientStop Color="#FF68B3DB"/>
    <GradientStop Color="#FF98D1EF" Offset="1"/>
</LinearGradientBrush>

<LinearGradientBrush x:Key="DefaultReflectVertical" 
              EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#B2FFFFFF" Offset="0"/>
    <GradientStop Color="#4CFFFFFF" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultReflectHorizontal" 
              EndPoint="1,0" StartPoint="0,0">
    <GradientStop Color="#B2FFFFFF" Offset="0"/>
    <GradientStop Color="#4CFFFFFF" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDarkGradientBottomVertical" 
              EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#005E5E5E" Offset="0"/>
    <GradientStop Color="#4C5C5C5C" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDarkGradientBottomHorizontal" 
              EndPoint="1,0" StartPoint="0,0">
    <GradientStop Color="#005E5E5E" Offset="0"/>
    <GradientStop Color="#4C5C5C5C" Offset="1"/>
</LinearGradientBrush>
<SolidColorBrush x:Key="DefaultToolBarBackgroundBrush" Color="White" />-->

<!--Grey / Yellow, Low reflect-->
<!--==========================-->
<!--<SolidColorBrush x:Key="DefaultForeground" Color="#FF4E4E4E" />
<Color x:Key="DefaultForegroundColor">#FF4E4E4E</Color>
<SolidColorBrush x:Key="DefaultShadow" Color="Transparent"/>
<SolidColorBrush x:Key="DefaultDisabled" Color="#FFF4F4F4"/>
<SolidColorBrush x:Key="DefaultFocus" Color="#FF3F3F3F"/>
<SolidColorBrush x:Key="DefaultScrollBackground" Color="#FFE9E9E9"/>
<SolidColorBrush x:Key="DefaultControlBackground" Color="White"/>
<SolidColorBrush x:Key="DefaultAlternativeBackground" Color="#FFE9E9E9"/>
<SolidColorBrush x:Key="DefaultListControlStroke" Color="#FFD2D2D2" />

<LinearGradientBrush x:Key="DefaultStroke" 
             EndPoint="0.5,1" StartPoint="0.5,0">
    <GradientStop Color="#FFCCCCCC"/>
    <GradientStop Color="#FF919191" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultBackground" 
             EndPoint="0.5,1" StartPoint="0.5,0">
    <GradientStop Color="#FFECECEC"/>
    <GradientStop Color="#FFAFAFAF" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultBackgroundHorizontal" 
             EndPoint="0,0" StartPoint="1,0">
    <GradientStop Color="#FFECECEC"/>
    <GradientStop Color="#FFAFAFAF" Offset="1"/>
</LinearGradientBrush>

<LinearGradientBrush x:Key="DefaultDownColor" 
             EndPoint="0.5,1" StartPoint="0.5,0">
    <GradientStop Color="#FFFFE588"/>
    <GradientStop Color="#FFFDC500" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDownColorHorizontal" 
             EndPoint="0,0" StartPoint="1,0">
    <GradientStop Color="#FFFFE588"/>
    <GradientStop Color="#FFFDC500" Offset="1"/>
</LinearGradientBrush>

<LinearGradientBrush x:Key="DefaultReflectVertical" 
             EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#4CFFFFFF" Offset="0"/>
    <GradientStop Color="#00FFFFFF" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultReflectHorizontal" 
             EndPoint="1,0" StartPoint="0,0">
    <GradientStop Color="#4CFFFFFF" Offset="0"/>
    <GradientStop Color="#00FFFFFF" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDarkGradientBottomVertical" 
             EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#005E5E5E" Offset="0"/>
    <GradientStop Color="#4C5C5C5C" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDarkGradientBottomHorizontal" 
             EndPoint="1,0" StartPoint="0,0">
   <GradientStop Color="#005E5E5E" Offset="0"/>
    <GradientStop Color="#4C5C5C5C" Offset="1"/>
</LinearGradientBrush>
<SolidColorBrush x:Key="DefaultToolBarBackgroundBrush" Color="#FFF2F2F2" />-->

<!--Grey / Blue reflect inverted, multi gradient-->
<!--============================================-->
<!--<SolidColorBrush x:Key="DefaultForeground" Color="#FF4E4E4E" />
<Color x:Key="DefaultForegroundColor">#FF4E4E4E</Color>
<SolidColorBrush x:Key="DefaultShadow" Color="Transparent"/>
<SolidColorBrush x:Key="DefaultDisabled" Color="#FFF4F4F4"/>
<SolidColorBrush x:Key="DefaultFocus" Color="#FF3F3F3F"/>
<SolidColorBrush x:Key="DefaultScrollBackground" Color="#FFE9E9E9"/>
<SolidColorBrush x:Key="DefaultControlBackground" Color="White"/>
<SolidColorBrush x:Key="DefaultAlternativeBackground" Color="#FFE9E9E9"/>
<SolidColorBrush x:Key="DefaultListControlStroke" Color="#FFD2D2D2" />

<LinearGradientBrush x:Key="DefaultStroke" 
              EndPoint="0.5,1" StartPoint="0.5,0">
    <GradientStop Color="#FFCCCCCC"/>
    <GradientStop Color="#FF919191" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultBackground" 
              EndPoint="0.5,1" StartPoint="0.5,0">
    <GradientStop Color="#FFB9B9B9"/>
    <GradientStop Color="#FFB9B9B9" Offset="1"/>
    <GradientStop Color="#FFD8D8D8" Offset="0.924"/>
    <GradientStop Color="#FFD8D8D8" Offset="0.062"/>
    <GradientStop Color="#FFFFFFFF" Offset="0.415"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultBackgroundHorizontal" 
              EndPoint="0,0" StartPoint="1,0">
    <GradientStop Color="#FFB9B9B9"/>
    <GradientStop Color="#FFB9B9B9" Offset="1"/>
    <GradientStop Color="#FFD8D8D8" Offset="0.924"/>
    <GradientStop Color="#FFD8D8D8" Offset="0.062"/>
    <GradientStop Color="#FFFFFFFF" Offset="0.415"/>
</LinearGradientBrush>

<LinearGradientBrush x:Key="DefaultDownColor" 
              EndPoint="0.5,1" StartPoint="0.5,0">
    <GradientStop Color="#FFD08400" Offset="0"/>
    <GradientStop Color="#FFD08400" Offset="1"/>
    <GradientStop Color="#FFF09900" Offset="0.075999997556209564"/>
    <GradientStop Color="#FFF09900" Offset="0.924"/>
    <GradientStop Color="#FFFBCB21" Offset="0.54000002145767212"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDownColorHorizontal" 
              EndPoint="0,0" StartPoint="1,0">
    <GradientStop Color="#FFD08400" Offset="0"/>
    <GradientStop Color="#FFD08400" Offset="1"/>
    <GradientStop Color="#FFF09900" Offset="0.075999997556209564"/>
    <GradientStop Color="#FFF09900" Offset="0.924"/>
    <GradientStop Color="#FFFBCB21" Offset="0.54000002145767212"/>
</LinearGradientBrush>

<LinearGradientBrush x:Key="DefaultReflectVertical" 
             EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#33FFFFFF" Offset="0"/>
    <GradientStop Color="#7FFFFFFF" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultReflectHorizontal" 
             EndPoint="1,0" StartPoint="0,0">
    <GradientStop Color="#33FFFFFF" Offset="0"/>
    <GradientStop Color="#7FFFFFFF" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDarkGradientBottomVertical" 
             EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#00000000" Offset="0"/>
    <GradientStop Color="#2D000000" Offset="1"/>
    <GradientStop Color="#19232323" Offset="0.87"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDarkGradientBottomHorizontal" 
             EndPoint="1,0" StartPoint="0,0">
   <GradientStop Color="#00000000" Offset="0"/>
    <GradientStop Color="#2D000000" Offset="1"/>
    <GradientStop Color="#19232323" Offset="0.87"/>
</LinearGradientBrush>
<SolidColorBrush x:Key="DefaultToolBarBackgroundBrush" Color="#FFF2F2F2" />-->

<!--Dark Grey Minimal-->
<!--=================-->
<!--<SolidColorBrush x:Key="DefaultForeground" Color="#FFC1C1C1" />
<Color x:Key="DefaultForegroundColor">#FFC1C1C1</Color>
<SolidColorBrush x:Key="DefaultFocus" Color="#FF656565" />
<SolidColorBrush x:Key="DefaultShadow" Color="#00272B2D"/>
<SolidColorBrush x:Key="DefaultDisabled" Color="#FF1C1C1C"/>
<SolidColorBrush x:Key="DefaultScrollBackground" Color="#FF434343"/>
<SolidColorBrush x:Key="DefaultControlBackground" Color="#FF3D3D3D"/>
<SolidColorBrush x:Key="DefaultStroke" Color="#FF292929" />
<SolidColorBrush x:Key="DefaultAlternativeBackground" Color="#FF535353"/>
<SolidColorBrush x:Key="DefaultListControlStroke" Color="#FF292929" />

<LinearGradientBrush x:Key="DefaultBackground" 
          EndPoint="0.5,1" StartPoint="0.5,0">
    <GradientStop Color="#FF494949" Offset="0"/>
    <GradientStop Color="#FF494949" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultBackgroundHorizontal" 
          EndPoint="0,0" StartPoint="1,0">
    <GradientStop Color="#FF494949" Offset="0"/>
    <GradientStop Color="#FF494949" Offset="1"/>
</LinearGradientBrush>

<LinearGradientBrush x:Key="DefaultDownColor" 
          EndPoint="0.5,1" StartPoint="0.5,0">
    <GradientStop Color="#FF313131"/>
    <GradientStop Color="#FF313131" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDownColorHorizontal" 
          EndPoint="0,0" StartPoint="1,0">
    <GradientStop Color="#FF313131"/>
    <GradientStop Color="#FF313131" Offset="1"/>
</LinearGradientBrush>

<LinearGradientBrush x:Key="DefaultReflectVertical" 
          EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#00FFFEED" Offset="0"/>
    <GradientStop Color="#00FFFFFF" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultReflectHorizontal" 
          EndPoint="1,0" StartPoint="0,0">
    <GradientStop Color="#00FFFEED" Offset="0"/>
    <GradientStop Color="#00FFFFFF" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDarkGradientBottomVertical" 
          EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#00000000" Offset="0"/>
    <GradientStop Color="#26FFFFFF" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDarkGradientBottomHorizontal" 
          EndPoint="1,0" StartPoint="0,0">
    <GradientStop Color="#00000000" Offset="0"/>
    <GradientStop Color="#26FFFFFF" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultToolBarBackgroundBrush" 
          EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#FF3D3D3D" Offset="0.3"/>
    <GradientStop Color="#FF3D3D3D" Offset="1"/>
    <GradientStop Color="#FF3D3D3D" Offset="0"/>
</LinearGradientBrush>-->

<!--Dark Red Grey Selected-->
<!--======================-->
<!--<SolidColorBrush x:Key="DefaultForeground" Color="#FF111111" />
<Color x:Key="DefaultForegroundColor">#FF111111</Color>
<SolidColorBrush x:Key="DefaultFocus" Color="#FF000000" />
<SolidColorBrush x:Key="DefaultShadow" Color="#00272B2D"/>
<SolidColorBrush x:Key="DefaultDisabled" Color="#FF494949"/>
<SolidColorBrush x:Key="DefaultScrollBackground" Color="#FF2D2D2D"/>
<SolidColorBrush x:Key="DefaultControlBackground" Color="#FF6E6E6E"/>
<SolidColorBrush x:Key="DefaultStroke" Color="#FF310000" />
<SolidColorBrush x:Key="DefaultAlternativeBackground" Color="#FF525252"/>
<SolidColorBrush x:Key="DefaultListControlStroke" Color="#FF383838" />

<LinearGradientBrush x:Key="DefaultBackground" 
           EndPoint="0.5,1" StartPoint="0.5,0">
    <GradientStop Color="#FFD61414"/>
    <GradientStop Color="#FF660A0A" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultBackgroundHorizontal" 
           EndPoint="0,0" StartPoint="1,0">
    <GradientStop Color="#FFD61414"/>
    <GradientStop Color="#FF660A0A" Offset="1"/>
</LinearGradientBrush>

<LinearGradientBrush x:Key="DefaultDownColor" 
           EndPoint="0.5,1" StartPoint="0.5,0">
    <GradientStop Color="#FF666666"/>
    <GradientStop Color="#FF666666" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDownColorHorizontal" 
           EndPoint="0,0" StartPoint="1,0">
    <GradientStop Color="#FF666666"/>
    <GradientStop Color="#FF666666" Offset="1"/>
</LinearGradientBrush>

<LinearGradientBrush x:Key="DefaultReflectVertical" 
           EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#26FFFFFF" Offset="1"/>
    <GradientStop Color="#4CFFFFFF" Offset="0"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultReflectHorizontal" 
           EndPoint="1,0" StartPoint="0,0">
    <GradientStop Color="#26FFFFFF" Offset="1"/>
    <GradientStop Color="#4CFFFFFF" Offset="0"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDarkGradientBottomVertical" 
           EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#00000000" Offset="0"/>
    <GradientStop Color="#34FFFFFF" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDarkGradientBottomHorizontal" 
           EndPoint="1,0" StartPoint="0,0">
    <GradientStop Color="#00000000" Offset="0"/>
    <GradientStop Color="#34FFFFFF" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultToolBarBackgroundBrush" 
           EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#FF6A6A6A" Offset="0"/>
    <GradientStop Color="#FF414141" Offset="1"/>
</LinearGradientBrush>-->

<!--Blue-->
<!--====-->
<!--<SolidColorBrush x:Key="DefaultForeground" Color="#FFFFFFFF" />
<Color x:Key="DefaultForegroundColor">#FFFFFFFF</Color>
<SolidColorBrush x:Key="DefaultFocus" Color="#FFFFFFFF" />
<SolidColorBrush x:Key="DefaultShadow" Color="#00272B2D"/>
<SolidColorBrush x:Key="DefaultDisabled" Color="#FF213957"/>
<SolidColorBrush x:Key="DefaultScrollBackground" Color="#FF606060"/>
<SolidColorBrush x:Key="DefaultControlBackground" Color="#FF919191"/>
<SolidColorBrush x:Key="DefaultStroke" Color="#FF273341" />
<SolidColorBrush x:Key="DefaultAlternativeBackground" Color="#FFA5A5A5"/>
<SolidColorBrush x:Key="DefaultListControlStroke" Color="#FF515151" />

<LinearGradientBrush x:Key="DefaultBackground" 
               EndPoint="0.5,1" StartPoint="0.5,0">
    <GradientStop Color="#FF153057"/>
    <GradientStop Color="#FF153057" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultBackgroundHorizontal" 
               EndPoint="0,0" StartPoint="1,0">
    <GradientStop Color="#FF153057"/>
    <GradientStop Color="#FF153057" Offset="1"/>
</LinearGradientBrush>

<LinearGradientBrush x:Key="DefaultDownColor" 
               EndPoint="0.5,1" StartPoint="0.5,0">
    <GradientStop Color="#FF00A3FF"/>
    <GradientStop Color="#FF002D4D" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDownColorHorizontal" 
               EndPoint="0,0" StartPoint="1,0">
    <GradientStop Color="#FF00A3FF"/>
    <GradientStop Color="#FF002D4D" Offset="1"/>
</LinearGradientBrush>

<LinearGradientBrush x:Key="DefaultReflectVertical" 
               EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#26FFFFFF" Offset="1"/>
    <GradientStop Color="#7FFFFFFF" Offset="0"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultReflectHorizontal" 
               EndPoint="1,0" StartPoint="0,0">
    <GradientStop Color="#26FFFFFF" Offset="1"/>
    <GradientStop Color="#7FFFFFFF" Offset="0"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDarkGradientBottomVertical" 
               EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#00000000" Offset="0"/>
    <GradientStop Color="#34000000" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDarkGradientBottomHorizontal" 
               EndPoint="1,0" StartPoint="0,0">
    <GradientStop Color="#00000000" Offset="0"/>
    <GradientStop Color="#34000000" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultToolBarBackgroundBrush" 
               EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#FF052841" Offset="0"/>
    <GradientStop Color="#FF052841" Offset="1"/>
</LinearGradientBrush>-->

<!--Black Selected-->
<!--==============-->
<!--<SolidColorBrush x:Key="DefaultForeground" Color="White" />
<Color x:Key="DefaultForegroundColor">White</Color>
<SolidColorBrush x:Key="DefaultFocus" Color="White" />
<SolidColorBrush x:Key="DefaultShadow" Color="#00272B2D"/>
<SolidColorBrush x:Key="DefaultDisabled" Color="#FF767870"/>
<SolidColorBrush x:Key="DefaultScrollBackground" Color="#FF2F2F2F"/>
<SolidColorBrush x:Key="DefaultControlBackground" Color="#FF4A4A4A"/>
<SolidColorBrush x:Key="DefaultStroke" Color="#FF313131" />
<SolidColorBrush x:Key="DefaultAlternativeBackground" Color="#FF2F2F2F"/>
<SolidColorBrush x:Key="DefaultListControlStroke" Color="#FF3A3A3A" />

<LinearGradientBrush x:Key="DefaultBackground" 
             EndPoint="0.5,1" StartPoint="0.5,0">
    <GradientStop Color="#FF9E9F7E" Offset="0"/>
    <GradientStop Color="#FF373737" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultBackgroundHorizontal" 
             EndPoint="0,0" StartPoint="1,0">
    <GradientStop Color="#FF9E9F7E" Offset="0"/>
    <GradientStop Color="#FF373737" Offset="1"/>
</LinearGradientBrush>

<LinearGradientBrush x:Key="DefaultDownColor" 
             EndPoint="0.5,1" StartPoint="0.5,0">
    <GradientStop Color="#FF3D3D3D"/>
    <GradientStop Color="#FF060606" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDownColorHorizontal" 
             EndPoint="0,0" StartPoint="1,0">
    <GradientStop Color="#FF3D3D3D"/>
    <GradientStop Color="#FF060606" Offset="1"/>
</LinearGradientBrush>

<LinearGradientBrush x:Key="DefaultReflectVertical" 
             EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#00FFFFFF" Offset="1"/>
    <GradientStop Color="#19FFFFFF" Offset="0"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultReflectHorizontal" 
             EndPoint="1,0" StartPoint="0,0">
    <GradientStop Color="#00FFFFFF" Offset="1"/>
    <GradientStop Color="#19FFFFFF" Offset="0"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDarkGradientBottomVertical" 
             EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#00000000" Offset="0"/>
    <GradientStop Color="#34FFFFFF" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDarkGradientBottomHorizontal" 
             EndPoint="1,0" StartPoint="0,0">
    <GradientStop Color="#00000000" Offset="0"/>
    <GradientStop Color="#34FFFFFF" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultToolBarBackgroundBrush" 
             EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#FF828169" Offset="0"/>
    <GradientStop Color="#FF4B4A3C" Offset="1"/>
</LinearGradientBrush>-->

<!--Purple & Grey-->
<!--=============-->
<!--<SolidColorBrush x:Key="DefaultForeground" Color="#FFFFFFFF" />
<Color x:Key="DefaultForegroundColor">#FFFFFFFF</Color>
<SolidColorBrush x:Key="DefaultFocus" Color="White" />
<SolidColorBrush x:Key="DefaultShadow" Color="#00272B2D"/>
<SolidColorBrush x:Key="DefaultDisabled" Color="#FF727272"/>
<SolidColorBrush x:Key="DefaultScrollBackground" Color="#FFA9A9A9"/>
<SolidColorBrush x:Key="DefaultControlBackground" Color="#FFB9B9B9"/>
<SolidColorBrush x:Key="DefaultStroke" Color="#FFDADADA" />
<SolidColorBrush x:Key="DefaultAlternativeBackground" Color="#FF979797"/>
<SolidColorBrush x:Key="DefaultListControlStroke" Color="#FFDADADA" />

<LinearGradientBrush x:Key="DefaultBackground" 
             EndPoint="0.5,1" StartPoint="0.5,0">
    <GradientStop Color="#FF7E7E7E" Offset="0"/>
    <GradientStop Color="#FF7E7E7E" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultBackgroundHorizontal" 
             EndPoint="0,0" StartPoint="1,0">
    <GradientStop Color="#FF7E7E7E" Offset="0"/>
    <GradientStop Color="#FF7E7E7E" Offset="1"/>
</LinearGradientBrush>

<LinearGradientBrush x:Key="DefaultDownColor" 
             EndPoint="0.5,1" StartPoint="0.5,0">
    <GradientStop Color="#FF2D162F"/>
    <GradientStop Color="#FF551A5C" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDownColorHorizontal" 
             EndPoint="0,0" StartPoint="1,0">
    <GradientStop Color="#FF2D162F"/>
    <GradientStop Color="#FF551A5C" Offset="1"/>
</LinearGradientBrush>

<LinearGradientBrush x:Key="DefaultReflectVertical" 
             EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#99FFFFFF" Offset="0"/>
    <GradientStop Color="#26FFFFFF" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultReflectHorizontal" 
             EndPoint="1,0" StartPoint="0,0">
    <GradientStop Color="#99FFFFFF" Offset="0"/>
    <GradientStop Color="#26FFFFFF" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDarkGradientBottomVertical" 
             EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#00000000" Offset="0"/>
    <GradientStop Color="#34FFFFFF" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDarkGradientBottomHorizontal" 
             EndPoint="1,0" StartPoint="0,0">
    <GradientStop Color="#00000000" Offset="0"/>
    <GradientStop Color="#34FFFFFF" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultToolBarBackgroundBrush" 
             EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#FF8F8F8F" Offset="0"/>
    <GradientStop Color="#FF8F8F8F" Offset="1"/>
</LinearGradientBrush>-->

<!--Dark Grey + Fire selected-->
<!--=========================-->
<!--<SolidColorBrush x:Key="DefaultForeground" Color="#FFFFFFFF" />
<Color x:Key="DefaultForegroundColor">#FFFFFFFF</Color>
<SolidColorBrush x:Key="DefaultFocus" Color="#FFFFFFFF" />
<SolidColorBrush x:Key="DefaultShadow" Color="#00272B2D"/>
<SolidColorBrush x:Key="DefaultDisabled" Color="#FF727272"/>
<SolidColorBrush x:Key="DefaultScrollBackground" Color="#FF8A8778"/>
<SolidColorBrush x:Key="DefaultControlBackground" Color="#FFA19E8B"/>
<SolidColorBrush x:Key="DefaultStroke" Color="#66FFFFFF" />
<SolidColorBrush x:Key="DefaultAlternativeBackground" Color="#FF8D8972"/>
<SolidColorBrush x:Key="DefaultListControlStroke" Color="#FF535353" />

<LinearGradientBrush x:Key="DefaultBackground" 
              EndPoint="0.5,1" StartPoint="0.5,0">
    <GradientStop Color="#FF7A7A7A" Offset="0"/>
    <GradientStop Color="#FF242424" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultBackgroundHorizontal" 
              EndPoint="0,0" StartPoint="1,0">
    <GradientStop Color="#FF7A7A7A" Offset="0"/>
    <GradientStop Color="#FF242424" Offset="1"/>
</LinearGradientBrush>

<LinearGradientBrush x:Key="DefaultDownColor" 
              EndPoint="0.5,1" StartPoint="0.5,0">
    <GradientStop Color="#FFF99502"/>
    <GradientStop Color="#FFCA4E20" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDownColorHorizontal" 
              EndPoint="0,0" StartPoint="1,0">
    <GradientStop Color="#FFF99502"/>
    <GradientStop Color="#FFCA4E20" Offset="1"/>
</LinearGradientBrush>

<LinearGradientBrush x:Key="DefaultReflectVertical" 
              EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#00FFFFFF" Offset="0"/>
    <GradientStop Color="#00FFFFFF" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultReflectHorizontal" 
              EndPoint="1,0" StartPoint="0,0">
    <GradientStop Color="#00FFFFFF" Offset="0"/>
    <GradientStop Color="#00FFFFFF" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDarkGradientBottomVertical" 
              EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#00FFFFFF" Offset="0"/>
    <GradientStop Color="#64C8C8C8" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDarkGradientBottomHorizontal" 
              EndPoint="1,0" StartPoint="0,0">
    <GradientStop Color="#00FFFFFF" Offset="0"/>
    <GradientStop Color="#64C8C8C8" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultToolBarBackgroundBrush" 
              EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#FF555450" Offset="1"/>
    <GradientStop Color="#FF848372" Offset="0"/>
</LinearGradientBrush>-->

展开父节点背景色

当我们构建Container_RowNodeStyle样式(当网格显示分层数据时,行使用的样式)时,我们使用了GoaOpen项目现有的Container_NodeStyle样式,并对其进行了少量增强,使其符合我们的需求。结果样式在节点展开和折叠时显示不同的背景。

Extended Parent Node BackColor

我们将删除此行为,因为它在像网格这样的复杂控件中使用时会增加混乱。

此外,我们将默认Indentation值增加到30,并更改ValidElementFocusElement的边距,使它们更易读。

找到generic.xaml文件末尾的Container_RowNodeStyle样式,并用此样式替换它:

<Style x:Key="Container_RowNodeStyle" TargetType="o:HandyListItem">
    <Setter Property="HorizontalAlignment" Value="Left" />
    <Setter Property="HorizontalContentAlignment" Value="Stretch" />
    <Setter Property="VerticalContentAlignment" Value="Center" />
    <Setter Property="Cursor" Value="Arrow" />
    <Setter Property="Padding" Value="0" />
    <Setter Property="Margin" Value="0"/>
    <Setter Property="Foreground" Value="{StaticResource DefaultForeground}"/>
    <Setter Property="Background" Value="{StaticResource DefaultControlBackground}" />
    <Setter Property="FontSize" Value="11" />
    <Setter Property="Indentation" Value="10" />
    <Setter Property="IsTabStop" Value="True" />
    <Setter Property="IsKeyActivable" Value="True"/>
    <Setter Property="ItemUnpressDropDownBehavior" Value="CloseAll" />
    <Setter Property="BorderBrush" Value="{StaticResource DefaultListControlStroke}"/>
    <Setter Property="BorderThickness" Value="1"/>
    <Setter Property="Indentation" Value="30"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="o:HandyListItem">
                <Grid x:Name="LayoutRoot">
                    <vsm:VisualStateManager.VisualStateGroups>
                        <vsm:VisualStateGroup x:Name="CommonStates">
                            <vsm:VisualState x:Name="Normal"/>
                            <vsm:VisualState x:Name="Disabled">
                                <Storyboard>
                                    <DoubleAnimation 
                                        Duration="0" 
                                        Storyboard.TargetName="ELEMENT_ContentPresenter" 
                                        Storyboard.TargetProperty="Opacity" 
                                        To="0.6"/>
                                    <DoubleAnimation 
                                        Duration="0" 
                                        Storyboard.TargetName="SelectedVisual" 
                                        Storyboard.TargetProperty="Opacity" 
                                        To="0.6"/>
                                    <ObjectAnimationUsingKeyFrames 
                                        Storyboard.TargetName="SelectedReflectVisual" 
                                        Storyboard.TargetProperty="Visibility" 
                                        Duration="0">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Visible</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                    <DoubleAnimation 
                                        Duration="0" 
                                        Storyboard.TargetName="HasItem" 
                                        Storyboard.TargetProperty="Opacity" 
                                        To="0.6"/>
                                </Storyboard>
                            </vsm:VisualState>
                        </vsm:VisualStateGroup>
                        <vsm:VisualStateGroup x:Name="FocusStates">
                            <vsm:VisualState x:Name="NotFocused"/>
                            <vsm:VisualState x:Name="Focused">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames 
                                        Storyboard.TargetName="FocusVisual" 
                                        Storyboard.TargetProperty="Visibility" 
                                        Duration="0">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Visible</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </vsm:VisualState>
                        </vsm:VisualStateGroup>
                        <vsm:VisualStateGroup x:Name="MouseOverStates">
                            <vsm:VisualState x:Name="NotMouseOver"/>
                            <vsm:VisualState x:Name="MouseOver">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames 
                                        Storyboard.TargetName="MouseOverVisual" 
                                        Storyboard.TargetProperty="Visibility" 
                                        Duration="0">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Visible</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </vsm:VisualState>
                        </vsm:VisualStateGroup>
                        <vsm:VisualStateGroup x:Name="PressedStates">
                            <vsm:VisualState x:Name="NotPressed"/>
                            <vsm:VisualState x:Name="Pressed">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames 
                                        Storyboard.TargetName="PressedVisual" 
                                        Storyboard.TargetProperty="Visibility" 
                                        Duration="0">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Visible</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </vsm:VisualState>
                        </vsm:VisualStateGroup>
                        <vsm:VisualStateGroup x:Name="SelectedStates">
                            <vsm:VisualState x:Name="NotSelected"/>
                            <vsm:VisualState x:Name="Selected">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames 
                                        Storyboard.TargetName="SelectedVisual" 
                                        Storyboard.TargetProperty="Visibility" 
                                        Duration="0">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Visible</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </vsm:VisualState>
                        </vsm:VisualStateGroup>
                        <vsm:VisualStateGroup x:Name="HasItemsStates">
                            <vsm:VisualState x:Name="NotHasItems"/>
                            <vsm:VisualState x:Name="HasItems">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames 
                                        Storyboard.TargetName="HasItem" 
                                        Storyboard.TargetProperty="Visibility" 
                                        Duration="0">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Visible</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </vsm:VisualState>
                        </vsm:VisualStateGroup>
                        <vsm:VisualStateGroup x:Name="IsExpandedStates">
                            <vsm:VisualState x:Name="NotIsExpanded"/>
                            <vsm:VisualState x:Name="IsExpanded">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames 
                                        Storyboard.TargetName="CheckedArrow" 
                                        Storyboard.TargetProperty="Visibility" 
                                        Duration="0">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Visible</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames 
                                        Storyboard.TargetName="ArrowUnchecked" 
                                        Storyboard.TargetProperty="Visibility" 
                                        Duration="0">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Collapsed</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </vsm:VisualState>
                        </vsm:VisualStateGroup>
                        <vsm:VisualStateGroup x:Name="AlternateStates">
                            <vsm:VisualState x:Name="NotIsAlternate"/>
                            <vsm:VisualState x:Name="IsAlternate">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames 
                                        Storyboard.TargetName="AlternateBackgroundVisual" 
                                        Storyboard.TargetProperty="Visibility" 
                                        Duration="0">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Visible</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames 
                                        Storyboard.TargetName="BackgroundVisual" 
                                        Storyboard.TargetProperty="Visibility" 
                                        Duration="0">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Collapsed</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </vsm:VisualState>
                        </vsm:VisualStateGroup>
                        <vsm:VisualStateGroup x:Name="InvertedStates">
                            <vsm:VisualState x:Name="InvertedItemsFlowDirection">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames 
                                        Storyboard.TargetName="ArrowCheckedToTop" 
                                        Storyboard.TargetProperty="Visibility" 
                                        Duration="0">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Visible</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames 
                                        Storyboard.TargetName="ArrowCheckedToBottom" 
                                        Storyboard.TargetProperty="Visibility" 
                                        Duration="0">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Collapsed</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </vsm:VisualState>
                            <vsm:VisualState x:Name="NormalItemsFlowDirection"/>
                        </vsm:VisualStateGroup>
                        <vsm:VisualStateGroup x:Name="ValidStates">
                            <vsm:VisualState x:Name="Valid"/>
                            <vsm:VisualState x:Name="NotValid">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames 
                                        Storyboard.TargetName="ValidElement" 
                                        Storyboard.TargetProperty="Visibility" 
                                        Duration="0">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Visible</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </vsm:VisualState>
                        </vsm:VisualStateGroup>
                    </vsm:VisualStateManager.VisualStateGroups>
                    <StackPanel Orientation="Horizontal" >
                        <Rectangle Width="{TemplateBinding FullIndentation}" />
                        <Grid MinWidth="22" Margin="0,0,1,0">
                            <Grid x:Name="HasItem" 
                                  Visibility="Collapsed" 
                                  Height="16" Width="16" 
                                  Margin="0,0,0,0"
                                  VerticalAlignment="Bottom">
                                <Path x:Name="ArrowUnchecked" 
                                  HorizontalAlignment="Right" 
                                  Height="8" Width="8" 
                                  Fill="{StaticResource DefaultForeground}" 
                                  Stretch="Fill" 
                                  Data="M 4 0 L 8 4 L 4 8 Z" />
                                <Grid x:Name="CheckedArrow" Visibility="Collapsed">
                                    <Path x:Name="ArrowCheckedToTop" 
                                      HorizontalAlignment="Right" 
                                      Height="8" Width="8" 
                                      Fill="{StaticResource DefaultForeground}" 
                                      Stretch="Fill" 
                                      Data="M 8 4 L 0 4 L 4 0 z" 
                                      Visibility="Collapsed"/>
                                    <Path x:Name="ArrowCheckedToBottom" 
                                      HorizontalAlignment="Right" 
                                      Height="8" Width="8" 
                                      Fill="{StaticResource DefaultForeground}" 
                                      Stretch="Fill" 
                                      Data="M 0 4 L 8 4 L 4 8 Z" />
                                </Grid>
                                <ToggleButton 
                                  x:Name="ELEMENT_ExpandButton" 
                                  Height="16" Width="16"  
                                  Style="{StaticResource EmptyToggleButtonStyle}" 
                                  IsChecked="{TemplateBinding IsExpanded}" 
                                  IsThreeState="False" IsTabStop="False"/>
                            </Grid>
                        </Grid>
                        <g:GDockPanel Background="Transparent">
                            <Grid g:GDockPanel.Dock="Fill">
                                <Border x:Name="BackgroundVisual" 
                                    Background="{TemplateBinding Background}" />
                                <Rectangle 
                                   Fill="{StaticResource DefaultAlternativeBackground}" 
                                   x:Name="AlternateBackgroundVisual" 
                                   Visibility="Collapsed"/>
                                <Grid x:Name="SelectedVisual" 
                                  Visibility="Collapsed" >
                                    <Grid.RowDefinitions>
                                        <RowDefinition Height="1*"/>
                                        <RowDefinition Height="1*"/>
                                    </Grid.RowDefinitions>
                                    <Rectangle Fill="{StaticResource DefaultDownColor}" 
                                           Grid.RowSpan="2"/>
                                    <Rectangle 
                                       x:Name="SelectedReflectVisual" 
                                       Fill="{StaticResource DefaultReflectVertical}" 
                                       Margin="0,1,1,0" 
                                       RadiusX="1" RadiusY="1"/>
                            </Grid>
                            <Rectangle 
                               x:Name="MouseOverVisual" 
                               Fill="{StaticResource 
                                      DefaultDarkGradientBottomVertical}" 
                               Visibility="Collapsed" Margin="0,0,1,0"/>
                            <Grid x:Name="PressedVisual" Visibility="Collapsed">
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="1*"/>
                                    <RowDefinition Height="1*"/>
                                </Grid.RowDefinitions>
                                <Rectangle 
                                    Fill="{StaticResource DefaultDownColor}" 
                                    Grid.RowSpan="2"/>
                                <Rectangle 
                                    Fill="{StaticResource 
                                           DefaultDarkGradientBottomVertical}" 
                                    Grid.Row="1" Margin="0,0,1,0"/>
                                <Rectangle 
                                    Fill="{StaticResource DefaultReflectVertical}" 
                                    Margin="0,1,1,0" 
                                    RadiusX="1" RadiusY="1"/>
                            </Grid>
                                <Rectangle 
                                   HorizontalAlignment="Stretch" 
                                   VerticalAlignment="Top" 
                                   Stroke="{TemplateBinding BorderBrush}" 
                                   StrokeThickness="0.5" 
                                   Height="1"/>
                                <Rectangle 
                                   Name="ValidElement" 
                                   Stroke="Red" 
                                   StrokeThickness="1.5"
                                   IsHitTestVisible="false" 
                                   Visibility="Collapsed"
                                   Margin="1,2,2,1"/>
                                <Rectangle 
                                   x:Name="FocusVisual" 
                                   Stroke="{StaticResource DefaultFocus}" 
                                   StrokeDashCap="Round" Margin="1,2,2,1" 
                                   StrokeDashArray=".2 2" 
                                   Visibility="Collapsed"/>
                                <g:GContentPresenter
                                  x:Name="ELEMENT_ContentPresenter"
                                  Content="{TemplateBinding Content}"
                                  ContentTemplate="{TemplateBinding ContentTemplate}"
                                  Cursor="{TemplateBinding Cursor}"
                                  OrientatedHorizontalAlignment=
                                    "{TemplateBinding HorizontalContentAlignment}"
                                  OrientatedMargin="{TemplateBinding Padding}"
                                  OrientatedVerticalAlignment=
                                    "{TemplateBinding VerticalContentAlignment}" 
                                  PresenterOrientation=
                                    "{TemplateBinding PresenterOrientation}"/>
                                <Rectangle 
                                   x:Name="BorderElement" 
                                   Stroke="{TemplateBinding BorderBrush}" 
                                   StrokeThickness="{TemplateBinding BorderThickness}" 
                                   Margin="-1,0,0,-1"/>
                            </Grid>
                        </g:GDockPanel>
                    </StackPanel>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

父节点和子节点之间的分隔符

父节点和子节点使用不同的缩进显示,以便数据的层次结构在网格中直观显示。尽管如此,我们希望通过在它们之间添加一个空格来更明显地区分父节点和子节点。

为了实现此功能,我们可以使用网格中显示的项(即行)的“NodeLevelActionStates”状态。当一个节点是层次结构新级别的第一个节点时,其NodeLevelActionStates状态为“JumpLevelNode”。否则,其NodeLevelActionStates状态为“NormalLevelNode”。

Alternate Type

让我们使用这些状态在项的顶部显示一个空格,当该项是新级别的第一个项时。

Container_RowNodeStyle样式中,在VisualStateManager.VisualStateGroups的末尾,让我们添加一个新的VisualStateGroup

<vsm:VisualStateGroup x:Name="NodeLevelActionStates">
    <vsm:VisualState x:Name="NormalLevelNode"/>
    <vsm:VisualState x:Name="JumpLevelNode">
        <Storyboard>
            <ObjectAnimationUsingKeyFrames 
                Storyboard.TargetName="NodeSpacerRectangle" 
                Storyboard.TargetProperty="Visibility" 
                Duration="0">
                <DiscreteObjectKeyFrame KeyTime="0">
                    <DiscreteObjectKeyFrame.Value>
                        <Visibility>Visible</Visibility>
                    </DiscreteObjectKeyFrame.Value>
                </DiscreteObjectKeyFrame>
            </ObjectAnimationUsingKeyFrames>
        </Storyboard>
    </vsm:VisualState>
</vsm:VisualStateGroup>

让我们添加一个不可见的矩形,它将显示在项的顶部,并在它与前一个项之间创建空间。为此,我们将向项的“LayoutRoot”网格添加两行。第一行包含不可见的矩形,第二行包含项的StackPanel

</vsm:VisualStateManager.VisualStateGroups>
<Grid.RowDefinitions>
    <RowDefinition Height="*"/>
    <RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Rectangle x:Name="NodeSpacerRectangle" Height="6" 
           Grid.Row="0" Visibility="Collapsed"/>
<StackPanel Orientation="Horizontal" Grid.Row="1">
    <Rectangle Width="{TemplateBinding FullIndentation}" />
    <Grid MinWidth="22" Margin="0,0,1,0">

删除动画

显示简单控件时,动画很棒。然而,对于像我们的数据网格这样的高级控件,尤其是在显示带有内部标题的复杂分层数据时,Silverlight中的动画渲染速度太慢,效果也不吸引人。因此,我们暂时只删除动画。

找到generic.xaml文件末尾的GridBodyStyle样式。

修改ItemsPanelModel的setter,以从GStackPanelModel中删除ChildrenAnimator

<Setter Property="ItemsPanelModel">
    <Setter.Value>
        <g:GStackPanelModel>
            <g:GStackPanelModel.KeyNavigator>
                <o:GridSpatialNavigator/>
            </g:GStackPanelModel.KeyNavigator>
        </g:GStackPanelModel>
    </Setter.Value>
</Setter>

TextCell和CheckBoxCell

TextCell的文本离单元格边框太近了。让我们修改单元格的填充,使文本更易读。

我们还更改了焦点元素的边距,使其更明显。

<Style TargetType="o:TextCell">
    <Setter Property="Background" Value="Transparent"/>
    <Setter Property="BorderBrush" Value="{StaticResource DefaultListControlStroke}"/>
    <Setter Property="BorderThickness" Value="1"/>
    <Setter Property="Foreground" Value="{StaticResource DefaultForeground}"/>
    <Setter Property="HorizontalContentAlignment" Value="Stretch" />
    <Setter Property="VerticalContentAlignment" Value="Stretch" />
    <Setter Property="Cursor" Value="Arrow" />
    <Setter Property="Padding" Value="5,4,4,4" />
    <Setter Property="Width" Value="100"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="o:TextCell">
                <Grid>
                    <vsm:VisualStateManager.VisualStateGroups>
                        <vsm:VisualStateGroup x:Name="CommonStates">
                            <vsm:VisualState x:Name="Standard"/>
                            <vsm:VisualState x:Name="Focused">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames 
                                    Storyboard.TargetName="FocusElement" 
                                    Storyboard.TargetProperty="Visibility" 
                                    Duration="0">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Visible</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </vsm:VisualState>
                            <vsm:VisualState x:Name="Edited">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames 
                                             Storyboard.TargetName="TextElement" 
                                             Storyboard.TargetProperty="Visibility" 
                                             Duration="0">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Collapsed</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames 
                                    Storyboard.TargetName="FocusElement" 
                                    Storyboard.TargetProperty="Visibility" 
                                    Duration="0">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Visible</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </vsm:VisualState>
                        </vsm:VisualStateGroup>
                        <vsm:VisualStateGroup x:Name="ValidStates">
                            <vsm:VisualState x:Name="Valid"/>
                            <vsm:VisualState x:Name="NotValid">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames 
                                        Storyboard.TargetName="ValidElement" 
                                        Storyboard.TargetProperty="Visibility" 
                                        Duration="0">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Visible</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </vsm:VisualState>
                        </vsm:VisualStateGroup>
                    </vsm:VisualStateManager.VisualStateGroups>
                    <Rectangle 
                        Name="ValidElement" 
                        Stroke="Red"
                        StrokeThickness="1.5"
                        IsHitTestVisible="false" 
                        Visibility="Collapsed"
                        Margin="1,2,2,1" />
                    <Grid x:Name="TextContainerElement">
                        <TextBlock 
                        x:Name="TextElement" 
                        Text="{TemplateBinding Text}"
                        Margin="{TemplateBinding Padding}"
                        HorizontalAlignment=
                          "{TemplateBinding HorizontalContentAlignment}"
                        VerticalAlignment=
                          "{TemplateBinding VerticalContentAlignment}"/>
                    </Grid>
                    <Rectangle Name="FocusElement" 
                           Stroke="{StaticResource DefaultFocus}" 
                           StrokeThickness="1" 
                           IsHitTestVisible="false" 
                           StrokeDashCap="Round" 
                           Margin="1,2,2,1" 
                           StrokeDashArray=".2 2" 
                           Visibility="Collapsed" />
                    <Rectangle Name="CellRightBorder" 
                           Stroke="{TemplateBinding BorderBrush}" 
                           StrokeThickness="0.5" 
                           Width="1" 
                           HorizontalAlignment="Right"/>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

CheckBoxCell过于复杂。让我们简化它,使其更易读且渲染速度更快。

<Style TargetType="o:CheckBoxCell">
    <Setter Property="Background" Value="Transparent" />
    <Setter Property="BorderBrush" Value="{StaticResource DefaultListControlStroke}"/>
    <Setter Property="BorderThickness" Value="1"/>
    <Setter Property="Foreground" Value="{StaticResource DefaultForeground}"/>
    <Setter Property="Cursor" Value="Arrow" />
    <Setter Property="Width" Value="21"/>
    <Setter Property="Padding" Value="4,4,5,4"/>
    <Setter Property="HorizontalContentAlignment" Value="Left" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="o:CheckBoxCell">
                <Grid Background="Transparent">
                    <vsm:VisualStateManager.VisualStateGroups>
                        <vsm:VisualStateGroup x:Name="CommonStates">
                            <vsm:VisualState x:Name="Standard"/>
                            <vsm:VisualState x:Name="Focused">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames 
                                    Storyboard.TargetName="focusElement" 
                                    Storyboard.TargetProperty="Visibility" 
                                    Duration="0">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Visible</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </vsm:VisualState>
                            <vsm:VisualState x:Name="Edited">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames 
                                    Storyboard.TargetName="focusElement" 
                                    Storyboard.TargetProperty="Visibility" 
                                    Duration="0">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Visible</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </vsm:VisualState>
                        </vsm:VisualStateGroup>
                        <vsm:VisualStateGroup x:Name="ValidStates">
                            <vsm:VisualState x:Name="Valid"/>
                            <vsm:VisualState x:Name="NotValid">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames 
                                    Storyboard.TargetName="ValidElement" 
                                    Storyboard.TargetProperty="Visibility" 
                                    Duration="0">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Visible</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </vsm:VisualState>
                        </vsm:VisualStateGroup>
                    </vsm:VisualStateManager.VisualStateGroups>
                    <Rectangle Name="ValidElement" 
                           Stroke="Red" 
                           StrokeThickness="2"
                           Margin="1,2,2,1" 
                           IsHitTestVisible="false"
                           Visibility="Collapsed"/>
                    <Grid HorizontalAlignment=
                                "{TemplateBinding HorizontalContentAlignment}">
                        <Border 
                        x:Name="BackgroundVisual" 
                        Background="{TemplateBinding Background}" 
                        Height="12" 
                        Width="12" 
                        BorderBrush="{TemplateBinding BorderBrush}" 
                        CornerRadius="1" 
                        BorderThickness="{TemplateBinding BorderThickness}"
                        Margin="{TemplateBinding Padding}"/>
                        <Grid 
                        x:Name="CheckMark" 
                        Width="9" 
                        Height="9" 
                        Visibility="{TemplateBinding CheckMarkVisibility}" >
                            <Path 
                        Stretch="Fill" 
                        Stroke="{TemplateBinding Foreground}" 
                        StrokeThickness="2" 
                        Data="M129.13295,140.87834 L132.875,145 L139.0639,137" />
                        </Grid>                            
                    </Grid>
                    <Rectangle 
                            Name="focusElement" 
                            Stroke="{StaticResource DefaultFocus}" 
                            StrokeThickness="1" 
                            Fill="{TemplateBinding Background}" 
                            IsHitTestVisible="false" 
                            StrokeDashCap="Round" 
                            Margin="1,2,2,1" 
                            StrokeDashArray=".2 2" 
                            Visibility="Collapsed" />
                    <Rectangle 
                        Name="CellRightBorder" 
                        Stroke="{TemplateBinding BorderBrush}" 
                        StrokeThickness="0.5" 
                        Width="1" 
                        HorizontalAlignment="Right"
                        Margin="0,-1,0,-1"/>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Container_RowItemStyle

让我们对Container_RowItemStyle应用类似的更改,使其更易读。我们将修改SelectedVisualReflectVisualValidElementFocusVisual的边距。

<Style x:Key="Container_RowItemStyle" TargetType="o:HandyListItem">
    <Setter Property="HorizontalAlignment" Value="Left" />
    <Setter Property="HorizontalContentAlignment" Value="Stretch" />
    <Setter Property="VerticalContentAlignment" Value="Center" />
    <Setter Property="Cursor" Value="Arrow" />
    <Setter Property="Padding" Value="0" />
    <Setter Property="Margin" Value="0"/>
    <Setter Property="Background" 
       Value="{StaticResource DefaultControlBackground}" />
    <Setter Property="Foreground" 
       Value="{StaticResource DefaultForeground}"/>
    <Setter Property="FontSize" Value="11" />
    <Setter Property="Indentation" Value="10" />
    <Setter Property="IsTabStop" Value="True" />
    <Setter Property="IsKeyActivable" Value="True"/>
    <Setter Property="ItemUnpressDropDownBehavior" Value="CloseAll" />
    <Setter Property="BorderBrush" 
       Value="{StaticResource DefaultListControlStroke}"/>
    <Setter Property="BorderThickness" Value="1"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="o:HandyListItem">
                <Grid Background="Transparent" x:Name="LayoutRoot">
                    <vsm:VisualStateManager.VisualStateGroups>
                        <vsm:VisualStateGroup x:Name="CommonStates">
                            <vsm:VisualState x:Name="Normal"/>
                            <vsm:VisualState x:Name="Disabled">
                                <Storyboard>
                                    <DoubleAnimation 
                                        Duration="0" 
                                        Storyboard.TargetName="ELEMENT_ContentPresenter" 
                                        Storyboard.TargetProperty="Opacity" To="0.6"/>
                                    <DoubleAnimation 
                                        Duration="0" 
                                        Storyboard.TargetName="SelectedVisual" 
                                        Storyboard.TargetProperty="Opacity" To="0.6"/>
                                    <DoubleAnimation 
                                        Duration="0" 
                                        Storyboard.TargetName="ReflectVisual" 
                                        Storyboard.TargetProperty="Opacity" To="0"/>
                                </Storyboard>
                            </vsm:VisualState>
                        </vsm:VisualStateGroup>
                        <vsm:VisualStateGroup x:Name="FocusStates">
                            <vsm:VisualState x:Name="NotFocused"/>
                            <vsm:VisualState x:Name="Focused">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames 
                                        Storyboard.TargetName="FocusVisual" 
                                        Storyboard.TargetProperty="Visibility" 
                                        Duration="0">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Visible</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </vsm:VisualState>
                        </vsm:VisualStateGroup>
                        <vsm:VisualStateGroup x:Name="MouseOverStates">
                            <vsm:VisualState x:Name="NotMouseOver"/>
                            <vsm:VisualState x:Name="MouseOver">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames 
                                        Storyboard.TargetName="MouseOverVisual" 
                                        Storyboard.TargetProperty="Visibility" 
                                        Duration="0">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Visible</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </vsm:VisualState>
                        </vsm:VisualStateGroup>
                        <vsm:VisualStateGroup x:Name="PressedStates">
                            <vsm:VisualState x:Name="NotPressed"/>
                            <vsm:VisualState x:Name="Pressed">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames 
                                        Storyboard.TargetName="PressedVisual" 
                                        Storyboard.TargetProperty="Visibility" 
                                        Duration="0">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Visible</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </vsm:VisualState>
                        </vsm:VisualStateGroup>
                        <vsm:VisualStateGroup x:Name="SelectedStates">
                            <vsm:VisualState x:Name="NotSelected"/>
                            <vsm:VisualState x:Name="Selected">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames 
                                        Storyboard.TargetName="SelectedVisual" 
                                        Storyboard.TargetProperty="Visibility" 
                                        Duration="0">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Visible</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames 
                                        Storyboard.TargetName="ReflectVisual" 
                                        Storyboard.TargetProperty="Visibility" 
                                        Duration="0">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Visible</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </vsm:VisualState>
                        </vsm:VisualStateGroup>
                        <vsm:VisualStateGroup x:Name="AlternateStates">
                            <vsm:VisualState x:Name="NotIsAlternate"/>
                            <vsm:VisualState x:Name="IsAlternate">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames 
                                        Storyboard.TargetName="AlternateBackgroundVisual" 
                                        Storyboard.TargetProperty="Visibility" 
                                        Duration="0">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Visible</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames 
                                        Storyboard.TargetName="BackgroundVisual" 
                                        Storyboard.TargetProperty="Visibility" 
                                        Duration="0">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Collapsed</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </vsm:VisualState>
                        </vsm:VisualStateGroup>
                        <vsm:VisualStateGroup x:Name="OrientationStates">
                            <vsm:VisualState x:Name="Horizontal"/>
                            <vsm:VisualState x:Name="Vertical"/>
                        </vsm:VisualStateGroup>
                        <vsm:VisualStateGroup x:Name="ValidStates">
                            <vsm:VisualState x:Name="Valid"/>
                            <vsm:VisualState x:Name="NotValid">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames 
                                        Storyboard.TargetName="ValidElement" 
                                        Storyboard.TargetProperty="Visibility" 
                                        Duration="0">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Visible</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </vsm:VisualState>
                        </vsm:VisualStateGroup>
                    </vsm:VisualStateManager.VisualStateGroups>
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="1*"/>
                            <RowDefinition Height="1*"/>
                        </Grid.RowDefinitions>
                        <Border x:Name="BackgroundVisual" 
                            Background="{TemplateBinding Background}" 
                            Grid.RowSpan="2" />
                        <Border x:Name="AlternateBackgroundVisual" 
                            Background="{StaticResource 
                                         DefaultAlternativeBackground}" 
                            Grid.RowSpan="2" 
                            Visibility="Collapsed"/>
                        <Rectangle x:Name="SelectedVisual" 
                               Fill="{StaticResource DefaultDownColor}" 
                               Grid.RowSpan="2" 
                                Margin="0,0,1,0"
                               Visibility="Collapsed"/>
                        <Rectangle x:Name="MouseOverVisual" 
                               Fill="{StaticResource 
                                      DefaultDarkGradientBottomVertical}" 
                               Grid.RowSpan="2" 
                               Margin="0,0,1,0"
                               Visibility="Collapsed"/>
                        <Grid x:Name="PressedVisual" 
                          Visibility="Collapsed" 
                          Grid.RowSpan="2" >
                            <Grid.RowDefinitions>
                                <RowDefinition Height="1*"/>
                                <RowDefinition Height="1*"/>
                            </Grid.RowDefinitions>
                            <Rectangle 
                                Fill="{StaticResource 
                                       DefaultDarkGradientBottomVertical}" 
                                Grid.Row="1" 
                                Margin="0,0,1,0" />
                        </Grid>
                        <Rectangle 
                            x:Name="ReflectVisual" 
                            Fill="{StaticResource DefaultReflectVertical}" 
                            Margin="0,1,1,0" 
                            Visibility="Collapsed"/>
                        <Rectangle Name="ValidElement" 
                               Stroke="Red" 
                               StrokeThickness="1.5"
                               IsHitTestVisible="false" 
                               Visibility="Collapsed"
                               Margin="1,2,2,1"
                               Grid.RowSpan="2"/>
                        <Rectangle 
                            x:Name="FocusVisual" 
                            Grid.RowSpan="2" 
                            Stroke="{StaticResource DefaultFocus}" 
                            StrokeDashCap="Round" 
                            Margin="2,2,2,1" 
                            StrokeDashArray=".2 2" 
                            Visibility="Collapsed"/>
                        <!-- Item content -->
                        <g:GContentPresenter
                          Grid.RowSpan="2" 
                          x:Name="ELEMENT_ContentPresenter"
                          Content="{TemplateBinding Content}"
                          ContentTemplate="{TemplateBinding ContentTemplate}"
                          OrientatedHorizontalAlignment=
                            "{TemplateBinding HorizontalContentAlignment}"
                          OrientatedMargin="{TemplateBinding Padding}"
                          OrientatedVerticalAlignment=
                            "{TemplateBinding VerticalContentAlignment}"  
                          PresenterOrientation=
                            "{TemplateBinding PresenterOrientation}"/>
                        <Rectangle x:Name="BorderElement" 
                               Grid.RowSpan="2" 
                               Stroke="{TemplateBinding BorderBrush}" 
                               StrokeThickness=
                                 "{TemplateBinding BorderThickness}" 
                               Margin="-1,0,0,-1"/>
                    </Grid>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

数据样本

到目前为止,在我们的GridBody项目中,我们使用循环生成数据。这是在网格中显示数据的简便方法。让我们改进数据生成方式,使其更接近可以在网格中显示的数据。

假设我们经营一家国际公司,员工遍布世界各地。每位员工都有一个费率。我们希望网格显示以下层次结构:

Countries
    Regions
        Employees

对于每个国家和地区,网格必须显示总费率,即该国家或地区所有员工费率的总和。

New Hierarchy

让我们首先修改GridBody项目的Person类。

using Open.Windows.Controls;
using System;

namespace GridBody
{
    public class Person : ContainerDataItem
    {
        public Person(string id, string firstName, string lastName, 
                      string address, string city, string zipCode, 
                      string stateID, string countryID, 
                      double rate, bool isCustomer)
        {
            this.firstName = firstName;
            this.lastName = lastName;
            this.address = address;
            this.city = city;
            this.zipCode = zipCode;
            this.isCustomer = isCustomer;
            this.stateID = stateID;
            this.rate = rate;
        }

        private string firstName;
        public string FirstName
        {
            get { return firstName; }
            set
            {
                if (firstName != value)
                {
                    firstName = value;
                    OnPropertyChanged("FirstName");
                }
            }
        }

        private string lastName;
        public string LastName
        {
            get { return lastName; }
            set
            {
                if (lastName != value)
                {
                    if (string.IsNullOrEmpty(value))
                        throw new Exception("Last name cannot be empty");

                    lastName = value;
                    OnPropertyChanged("LastName");
                }
            }
        }

        private string address;
        public string Address
        {
            get { return address; }
            set
            {
                if (address != value)
                {
                    address = value;
                    OnPropertyChanged("Address");
                }
            }
        }

        private string city;
        public string City
        {
            get { return city; }
            set
            {
                if (city != value)
                {
                    city = value;
                    OnPropertyChanged("City");
                }
            }
        }

        private string zipCode;
        public string ZipCode
        {
            get { return zipCode; }
            set
            {
                if (zipCode != value)
                {
                    zipCode = value;
                    OnPropertyChanged("ZipCode");
                }
            }
        }

        private bool isCustomer;
        public bool IsCustomer
        {
            get { return isCustomer; }
            set
            {
                if (isCustomer != value)
                {
                    isCustomer = value;
                    OnPropertyChanged("IsCustomer");
                }
            }
        }

        private string stateID;
        public string StateID
        {
            get { return stateID; }
            set
            {
                if (stateID != value)
                {
                    stateID = value;
                    OnPropertyChanged("StateID");
                }
            }
        }

        private double rate;
        public double Rate
        {
            get { return rate; }
            set
            {
                if (rate != value)
                {
                    rate = value;
                    OnPropertyChanged("Rate");
                }
            }
        }

        public bool Validate()
        {
            int zipCodeValue = 0;
            int.TryParse(zipCode, out zipCodeValue);
            if ((city.ToUpper() == "NEW YORK") && 
                ((zipCodeValue < 10001) || (zipCodeValue > 10292)))
                return false;

            return true;
        }       
    }
}

让我们添加一个新的StateProvince类:

using Open.Windows.Controls;
using System.Collections.Specialized;

namespace GridBody
{
    public class StateProvince : ContainerDataItem
    {
        public StateProvince(string code, string name, string countryRegionCode)
        {
            this.name = name;
            this.code = code;
            this.countryRegionCode = countryRegionCode;
            this.Children.CollectionChanged += new 
              System.Collections.Specialized.
              GNotifyCollectionChangedEventHandler(Children_CollectionChanged);

        }

        void Children_CollectionChanged(object sender, 
             System.Collections.Specialized.GNotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case GNotifyCollectionChangedAction.Add:
                    foreach (Person person in e.NewItems)
                    {
                        person.PropertyChanged += new 
                          System.ComponentModel.
                          PropertyChangedEventHandler(person_PropertyChanged);
                        this.Rate += person.Rate;
                    }

                    break;

                case GNotifyCollectionChangedAction.Remove:
                    foreach (Person person in e.OldItems)
                    {
                        person.PropertyChanged -= new 
                          System.ComponentModel.
                          PropertyChangedEventHandler(person_PropertyChanged);
                        this.Rate -= person.Rate;
                    }

                    break;

                case GNotifyCollectionChangedAction.Replace:
                    foreach (Person person in e.NewItems)
                    {
                        person.PropertyChanged += new 
                          System.ComponentModel.
                          PropertyChangedEventHandler(person_PropertyChanged);
                        this.Rate += person.Rate;
                    }

                    foreach (Person person in e.OldItems)
                    {
                        person.PropertyChanged -= new 
                          System.ComponentModel.
                          PropertyChangedEventHandler(person_PropertyChanged);
                        this.Rate -= person.Rate;
                    }

                    break;

                case GNotifyCollectionChangedAction.Reset:
                    //TODO remove events
                    this.Rate = 0;
                    break;
            }
        }

        void person_PropertyChanged(object sender, 
             System.ComponentModel.PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "Rate")
            {
                double newRate = 0;
                foreach (Person person in this.Children)
                {
                    newRate += person.Rate;
                }

                this.Rate = newRate;
            }
        }


        private string name;
        public string Name
        {
            get { return name; }
            set
            {
                if (name != value)
                {
                    name = value;
                    OnPropertyChanged("Name");
                }
            }
        }

        private string code;
        public string Code
        {
            get { return code; }
            set
            {
                if (code != value)
                {
                    code = value;
                    OnPropertyChanged("Code");
                }
            }
        }

        private string countryRegionCode;
        public string CountryRegionCode
        {
            get { return countryRegionCode; }
            set
            {
                if (countryRegionCode != value)
                {
                    countryRegionCode = value;
                    OnPropertyChanged("CountryRegionCode");
                }
            }
        }

        private double rate;
        public double Rate
        {
            get { return rate; }
            set
            {
                if (rate != value)
                {
                    rate = value;
                    OnPropertyChanged("Rate");
                }
            }
        }
    }
}

然后,让我们修改Country类:

using Open.Windows.Controls;
using System.Collections.Specialized;

namespace GridBody
{    
    public class Country : ContainerDataItem
    {
        public Country(string code, string name)
        {
            this.name = name;
            this.code = code;


            this.Children.CollectionChanged += new 
              System.Collections.Specialized.
              GNotifyCollectionChangedEventHandler(Children_CollectionChanged);

        }

        void Children_CollectionChanged(object sender, 
             System.Collections.Specialized.GNotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case GNotifyCollectionChangedAction.Add:
                    foreach (StateProvince stateProvince in e.NewItems)
                    {
                        stateProvince.PropertyChanged += new 
                          System.ComponentModel.
                          PropertyChangedEventHandler(stateProvince_PropertyChanged);
                        this.Rate += stateProvince.Rate;
                    }

                    break;

                case GNotifyCollectionChangedAction.Remove:
                    foreach (StateProvince stateProvince in e.OldItems)
                    {
                        stateProvince.PropertyChanged -= new 
                          System.ComponentModel.
                          PropertyChangedEventHandler(stateProvince_PropertyChanged);
                        this.Rate -= stateProvince.Rate;
                    }

                    break;

                case GNotifyCollectionChangedAction.Replace:
                    foreach (StateProvince stateProvince in e.NewItems)
                    {
                        stateProvince.PropertyChanged += new 
                          System.ComponentModel.
                          PropertyChangedEventHandler(stateProvince_PropertyChanged);
                        this.Rate += stateProvince.Rate;
                    }

                    foreach (StateProvince stateProvince in e.OldItems)
                    {
                        stateProvince.PropertyChanged -= new 
                          System.ComponentModel.
                          PropertyChangedEventHandler(stateProvince_PropertyChanged);
                        this.Rate -= stateProvince.Rate;
                    }

                    break;

                case GNotifyCollectionChangedAction.Reset:
                    this.Rate = 0;
                    break;
            }
        }

        void stateProvince_PropertyChanged(object sender, 
             System.ComponentModel.PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "Rate")
            {
                double newRate = 0;
                foreach (StateProvince stateProvince in this.Children)
                {
                    newRate += stateProvince.Rate;
                }

                this.Rate = newRate;
            }
        }

        private string name;
        public string Name
        {
            get { return name; }
            set
            {
                if (name != value)
                {
                    name = value;
                    OnPropertyChanged("Name");
                }
            }
        }

        private string code;
        public string Code
        {
            get { return code; }
            set
            {
                if (code != value)
                {
                    code = value;
                    OnPropertyChanged("Code");
                }
            }
        }

        private double rate;
        public double Rate
        {
            get { return rate; }
            set
            {
                if (rate != value)
                {
                    rate = value;
                    OnPropertyChanged("Rate");
                }
            }
        }
    }
}

让我们修改Page.xaml.cs文件的构造函数,并添加一个新的stateProvinceCollection字段:

public partial class Page : UserControl
{
    private GObservableCollection<Person> personCollection;
    private GObservableCollection<StateProvince> stateProvinceCollection;
    private GObservableCollection<Country> countryCollection;

    public Page()
    {
        InitializeComponent();

        CreateData();
        MyGridBody.ItemsSource = countryCollection;            
    }
    ...

最后,让我们在Page类中添加一个CreateData方法:

private void CreateData()
{
    personCollection = new GObservableCollection<Person>();
    personCollection.Add(new Person("1001", "Terri", 
                         "Duffy", "7559 Worth Ct.", 
                         "Renton", "98055", 
                         "WA", "US", 63.4615, true));
    personCollection.Add(new Person("1002", "Roberto", 
                         "Tamburello", "2137 Birchwood Dr", 
                         "Redmond", "98052", 
                         "WA", "US", 43.2692, true));
    personCollection.Add(new Person("1003", "Michael", 
                         "Sullivan", "6510 Hacienda Drive", 
                         "Renton", "98055", 
                         "WA", "US", 36.0577, false));
    personCollection.Add(new Person("1004", "Sharon", 
                         "Salavaria", "7165 Brock Lane", 
                         "Renton", "98055", "WA", 
                         "US", 32.6923, true));
    personCollection.Add(new Person("1005", "Gail", 
                         "Erickson", "9435 Breck Court", 
                         "Bellevue", "98004", 
                         "WA", "US", 32.6923, true));
    personCollection.Add(new Person("1061", "David", "Hamilton", 
                         "4095 Cooper Dr.", "Kenmore", "98028", 
                         "WA", "US", 25, true));
    personCollection.Add(new Person("1062", "Jeff", "Hay", 
                         "3385 Crestview Drive", "Everett", _
                         "98201", "WA", "US", 25, true));
    personCollection.Add(new Person("1063", "Shane", "Kim", 
                         "9745 Bonita Ct.", "Bellevue", 
                         "98004", "WA", "US", 25, true));
    .
    .
    .
    .
    .
    .
    personCollection.Add(new Person("1283", "Vamsi", "Kuppa", 
                         "9833 Mt. Dias Blv.", "Bothell", 
                         "98011", "WA", "US", 9.5, true));
    personCollection.Add(new Person("1284", "Jimmy", 
                         "Bischoff", "2176 Brown Street", 
                         "Renton", "98055", "WA", 
                         "US", 9, true));
    personCollection.Add(new Person("1285", "Susan", "Eaton", 
                         "2736 Scramble Rd", "Renton", 
                         "98055", "WA", "US", 9, true));
    personCollection.Add(new Person("1286", "Kim", "Ralls", 
                         "1226 Shoe St.", "Bothell", "98011", 
                         "WA", "US", 9, true));
    personCollection.Add(new Person("1287", "Ken", "S nchez", 
                         "4350 Minute Dr.", "Newport Hills", 
                         "98006", "WA", "US", 125.5, true));
    personCollection.Add(new Person("1288", "Laura", "Norman", 
                         "6937 E. 42nd Street", "Renton", 
                         "98055", "WA", "US", 39.06, false));
    personCollection.Add(new Person("1289", "Michael", "Raheem", 
                         "1234 Seaside Way", "San Francisco", 
                         "94109", "CA", "US", 42.4808, true));
    personCollection.Add(new Person("1290", "Rob", "Walters", 
                         "5678 Lakeview Blvd.", "Minneapolis", 
                         "55402", "MN", "US", 29.8462, true));


    stateProvinceCollection = new GObservableCollection<StateProvince>();
    stateProvinceCollection.Add(new StateProvince("AB", "Alberta", "CA"));
    stateProvinceCollection.Add(new StateProvince("AK", "Alaska", "US"));
    stateProvinceCollection.Add(new StateProvince("AL", "Alabama", "US"));
    stateProvinceCollection.Add(new StateProvince("AR", "Arkansas", "US"));
    stateProvinceCollection.Add(new StateProvince("AS", "American Samoa", "AS"));
    .
    .
    .
    .
    .
    stateProvinceCollection.Add(new StateProvince("88", "Vosges", "FR"));
    stateProvinceCollection.Add(new StateProvince("89", "Yonne", "FR"));
    stateProvinceCollection.Add(new StateProvince("90", "Belford (Territoire de)", "FR"));
    stateProvinceCollection.Add(new StateProvince("91", "Essonne", "FR"));
    stateProvinceCollection.Add(new StateProvince("92", "Hauts de Seine", "FR"));
    stateProvinceCollection.Add(new StateProvince("93", "Seine Saint Denis", "FR"));
    stateProvinceCollection.Add(new StateProvince("94", "Val de Marne", "FR"));
    stateProvinceCollection.Add(new StateProvince("95", "Val d'Oise", "FR"));

    countryCollection = new GObservableCollection<Country>();
    countryCollection.Add(new Country("AF", "Afghanistan"));
    countryCollection.Add(new Country("AL", "Albania"));
    countryCollection.Add(new Country("DZ", "Algeria"));
    .
    .
    .
    .
    countryCollection.Add(new Country("VE", "Venezuela"));
    countryCollection.Add(new Country("VN", "Vietnam"));
    countryCollection.Add(new Country("VG", "Virgin Islands, British"));
    countryCollection.Add(new Country("VI", "Virgin Islands, U.S."));
    countryCollection.Add(new Country("WF", "Wallis and Futuna"));
    countryCollection.Add(new Country("YE", "Yemen"));
    countryCollection.Add(new Country("ZM", "Zambia"));
    countryCollection.Add(new Country("ZW", "Zimbabwe"));


    foreach (Country country in countryCollection)
    {
        foreach (StateProvince stateProvince in stateProvinceCollection)
        {
            if (stateProvince.CountryRegionCode == country.Code)
                country.Children.Add(stateProvince);
        }
    }

    foreach (Person person in personCollection)
    {
        foreach (StateProvince stateProvince in stateProvinceCollection)
        {
            if (stateProvince.Code == person.StateID)
            {
                stateProvince.Children.Add(person);
                break;
            }
        }
    }
}

现在,让我们在Page.xaml文件中修改GridBodyItemTemplate属性:

<o:HandyContainer.ItemTemplate>
    <g:ItemDataTemplate>
        <Grid>
            <o:HandyDataPresenter DataType="GridBody.Person">
                <g:GStackPanel Orientation="Horizontal">
                    <g:GStackPanel.KeyNavigator>
                        <o:RowSpatialNavigator/>
                    </g:GStackPanel.KeyNavigator>
                    <o:TextCell 
                        Text="{Binding FirstName, Mode=TwoWay}" 
                        x:Name="FirstName"/>
                    <o:TextCell 
                        Text="{Binding LastName, Mode=TwoWay, 
                               NotifyOnValidationError=true, 
                               ValidatesOnExceptions=true}" 
                        x:Name="LastName"/>
                    <o:TextCell 
                        Text="{Binding Address, Mode=TwoWay}" 
                        x:Name="Address"/>
                    <o:TextCell 
                        Text="{Binding City, Mode=TwoWay}" 
                        x:Name="City"/>
                    <o:TextCell 
                        Text="{Binding ZipCode, Mode=TwoWay}" 
                        x:Name="ZipCode"/>
                    <o:CheckBoxCell 
                        IsChecked="{Binding IsCustomer, Mode=TwoWay}" 
                        x:Name="IsCustomer"/>
                    <o:TextCell 
                        Text="{Binding Rate, Mode=TwoWay}" 
                        x:Name="PersonRate"/>
                </g:GStackPanel>
            </o:HandyDataPresenter>
            <o:HandyDataPresenter DataType="GridBody.StateProvince">
                <g:GStackPanel Orientation="Horizontal">
                    <g:GStackPanel.KeyNavigator>
                        <o:RowSpatialNavigator/>
                    </g:GStackPanel.KeyNavigator>
                    <o:TextCell 
                        Text="{Binding Name, Mode=TwoWay}" 
                        x:Name="StateName"/>
                    <o:TextCell 
                        Text="{Binding Children.Count}" 
                        x:Name="ChildrenCount" 
                        CanEdit="False"/>
                    <o:TextCell 
                        Text="{Binding Rate}" 
                        x:Name="RegionRate"
                        CanEdit="False"/>
                </g:GStackPanel>
            </o:HandyDataPresenter>
            <o:HandyDataPresenter DataType="GridBody.Country">
                <g:GStackPanel Orientation="Horizontal">
                    <g:GStackPanel.KeyNavigator>
                        <o:RowSpatialNavigator/>
                    </g:GStackPanel.KeyNavigator>
                    <o:TextCell 
                        Text="{Binding Name, Mode=TwoWay}" 
                        x:Name="CountryName"/>
                    <o:TextCell 
                        Text="{Binding Children.Count}" 
                        x:Name="StateChildrenCount" 
                        CanEdit="False"/>
                    <o:TextCell 
                        Text="{Binding Rate}" 
                        x:Name="CountryRate"
                        CanEdit="False"/>
                </g:GStackPanel>
            </o:HandyDataPresenter>
        </Grid>
    </g:ItemDataTemplate>
</o:HandyContainer.ItemTemplate>

3. 添加标题

引言

当显示分层数据时,用户有时会觉得显示混乱。他们可能难以理解层次结构的各个级别。为了使分层数据更易读,我们将允许我们的网格在每个级别的顶部显示一个标题。

Title

ItemTitle控件

Control

让我们创建一个ItemTitle控件。此控件将显示在项的顶部。我们将使用项的NodeLevelActionStates状态,使其仅在项是层次结构级别跳转后的第一个项时出现。

让我们在GoaOpen\Extensions\Grid文件夹中添加ItemTitle类。

using Netika.Windows.Controls;

namespace Open.Windows.Controls
{
    public class ItemTitle : GContentControl
    {
        public ItemTitle()
        {
            this.DefaultStyleKey = typeof(ItemTitle);
        }        
    }
}

ItemTitle控件继承自GContentControl。这将允许我们显示几乎任何我们想要的内容(文本、图像等)。

样式

让我们创建ItemTitle控件的样式,并将其添加到generic.xaml文件中,放在Container_RowNodeStyle样式之前。

<Style TargetType="o:ItemTitle">
    <Setter Property="HorizontalAlignment" Value="Stretch" />
    <Setter Property="HorizontalContentAlignment" Value="Stretch" />
    <Setter Property="VerticalContentAlignment" Value="Center" />
    <Setter Property="Cursor" Value="Arrow" />
    <Setter Property="Margin" Value="0"/>
    <Setter Property="Background" 
      Value="{StaticResource DefaultDarkGradientBottomVertical}" />
    <Setter Property="Foreground" 
      Value="{StaticResource DefaultForeground}"/>
    <Setter Property="FontSize" Value="11" />
    <Setter Property="IsTabStop" Value="False" />
    <Setter Property="BorderBrush" 
      Value="{StaticResource DefaultListControlStroke}"/>
    <Setter Property="BorderThickness" Value="1"/>
    <Setter Property="Padding" Value="5,4,4,3" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="o:ItemTitle">
                <Grid x:Name="LayoutRoot">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="1*" />
                        <RowDefinition Height="1*" />
                    </Grid.RowDefinitions>
                    <Rectangle x:Name="BackgroundVisual" 
                      Fill="{TemplateBinding Background}" 
                      Grid.RowSpan="2"/>
                    <Rectangle 
                      Fill="{StaticResource DefaultReflectVertical}" 
                      Margin="1,1,1,0" />
                    <g:GContentPresenter
                      x:Name="ELEMENT_ContentPresenter"
                      Content="{TemplateBinding Content}"
                      ContentTemplate="{TemplateBinding ContentTemplate}"
                      OrientatedHorizontalAlignment="Left"
                      OrientatedMargin="{TemplateBinding Padding}"
                      OrientatedVerticalAlignment=
                        "{TemplateBinding VerticalContentAlignment}"  
                      PresenterOrientation=
                        "{TemplateBinding PresenterOrientation}"
                      Grid.RowSpan="2"/>
                    <Rectangle x:Name="BorderElement" 
                               Stroke="{TemplateBinding BorderBrush}" 
                               StrokeThickness=
                                 "{TemplateBinding BorderThickness}" 
                               Margin="-1,0,0,-1"
                               Grid.RowSpan="2"/>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

基本上,ItemTitle控件具有与标准GContentControl相同的样式,除了我们为其添加了背景和边框。

在Container_RowNodeStyle中添加ItemTitle

让我们在Container_RowNodeStyle中添加ItemTitle,以便它显示在项的顶部。

如果我们根据Container_RowNodeStyle样式的ControlTemplate部分绘制一张图,它看起来会是这样:

Container_RowNodeStyle

ItemTitle必须显示在ContainerItem内容的正上方。它必须位于节点展开按钮的右侧。因此,插入ItemTitle后,我们的图片将如下所示:

Container_RowNodeStyle With ItemTitle

让我们相应地修改Container_RowNodeStyle

<Style x:Key="Container_RowNodeStyle" TargetType="o:HandyListItem">
    <Setter Property="HorizontalAlignment" Value="Left" />
    <Setter Property="HorizontalContentAlignment" Value="Stretch" />
    <Setter Property="VerticalContentAlignment" Value="Center" />
    <Setter Property="Cursor" Value="Arrow" />
    <Setter Property="Padding" Value="0" />
    <Setter Property="Margin" Value="0"/>
    <Setter Property="Foreground" Value="{StaticResource DefaultForeground}"/>
    <Setter Property="Background" Value="{StaticResource DefaultControlBackground}" />
    <Setter Property="FontSize" Value="11" />
    <Setter Property="Indentation" Value="10" />
    <Setter Property="IsTabStop" Value="True" />
    <Setter Property="IsKeyActivable" Value="True"/>
    <Setter Property="ItemUnpressDropDownBehavior" Value="CloseAll" />
    <Setter Property="BorderBrush" Value="{StaticResource DefaultListControlStroke}"/>
    <Setter Property="BorderThickness" Value="1"/>
    <Setter Property="Indentation" Value="30"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="o:HandyListItem">
                <Grid x:Name="LayoutRoot">
                    <vsm:VisualStateManager.VisualStateGroups>
                        <vsm:VisualStateGroup x:Name="CommonStates">
                            <vsm:VisualState x:Name="Normal"/>
                            <vsm:VisualState x:Name="Disabled">
                                <Storyboard>
                                    <DoubleAnimation 
                                        Duration="0" 
                                        Storyboard.TargetName="ELEMENT_ContentPresenter" 
                                        Storyboard.TargetProperty="Opacity" To="0.6"/>
                                    <DoubleAnimation 
                                        Duration="0" 
                                        Storyboard.TargetName="SelectedVisual" 
                                        Storyboard.TargetProperty="Opacity" 
                                        To="0.6"/>
                                    <ObjectAnimationUsingKeyFrames 
                                        Storyboard.TargetName="SelectedReflectVisual" 
                                        Storyboard.TargetProperty="Visibility" 
                                        Duration="0">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Visible</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                    <DoubleAnimation 
                                        Duration="0" 
                                        Storyboard.TargetName="HasItem" 
                                        Storyboard.TargetProperty="Opacity" 
                                        To="0.6"/>
                                </Storyboard>
                            </vsm:VisualState>
                        </vsm:VisualStateGroup>
                        <vsm:VisualStateGroup x:Name="FocusStates">
                            <vsm:VisualState x:Name="NotFocused"/>
                            <vsm:VisualState x:Name="Focused">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames 
                                        Storyboard.TargetName="FocusVisual" 
                                        Storyboard.TargetProperty="Visibility" 
                                        Duration="0">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Visible</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </vsm:VisualState>
                        </vsm:VisualStateGroup>
                        <vsm:VisualStateGroup x:Name="MouseOverStates">
                            <vsm:VisualState x:Name="NotMouseOver"/>
                            <vsm:VisualState x:Name="MouseOver">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames 
                                        Storyboard.TargetName="MouseOverVisual" 
                                        Storyboard.TargetProperty="Visibility" 
                                        Duration="0">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Visible</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                        
                                </Storyboard>
                            </vsm:VisualState>
                        </vsm:VisualStateGroup>
                        <vsm:VisualStateGroup x:Name="PressedStates">
                            <vsm:VisualState x:Name="NotPressed"/>
                            <vsm:VisualState x:Name="Pressed">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames 
                                        Storyboard.TargetName="PressedVisual" 
                                        Storyboard.TargetProperty="Visibility" 
                                        Duration="0">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Visible</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </vsm:VisualState>
                        </vsm:VisualStateGroup>
                        <vsm:VisualStateGroup x:Name="SelectedStates">
                            <vsm:VisualState x:Name="NotSelected"/>
                            <vsm:VisualState x:Name="Selected">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames 
                                        Storyboard.TargetName="SelectedVisual" 
                                        Storyboard.TargetProperty="Visibility" 
                                        Duration="0">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Visible</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </vsm:VisualState>
                        </vsm:VisualStateGroup>
                        <vsm:VisualStateGroup x:Name="HasItemsStates">
                            <vsm:VisualState x:Name="NotHasItems"/>
                            <vsm:VisualState x:Name="HasItems">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames 
                                        Storyboard.TargetName="HasItem" 
                                        Storyboard.TargetProperty="Visibility" 
                                        Duration="0">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Visible</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>

                                </Storyboard>
                            </vsm:VisualState>
                        </vsm:VisualStateGroup>
                        <vsm:VisualStateGroup x:Name="IsExpandedStates">
                            <vsm:VisualState x:Name="NotIsExpanded"/>
                            <vsm:VisualState x:Name="IsExpanded">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames 
                                        Storyboard.TargetName="CheckedArrow" 
                                        Storyboard.TargetProperty="Visibility" 
                                        Duration="0">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Visible</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames 
                                        Storyboard.TargetName="ArrowUnchecked" 
                                        Storyboard.TargetProperty="Visibility" 
                                        Duration="0">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Collapsed</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </vsm:VisualState>
                        </vsm:VisualStateGroup>
                        <vsm:VisualStateGroup x:Name="AlternateStates">
                            <vsm:VisualState x:Name="NotIsAlternate"/>
                            <vsm:VisualState x:Name="IsAlternate">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames 
                                     Storyboard.TargetName="AlternateBackgroundVisual" 
                                     Storyboard.TargetProperty="Visibility" 
                                     Duration="0">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Visible</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames 
                                        Storyboard.TargetName="BackgroundVisual" 
                                        Storyboard.TargetProperty="Visibility" 
                                        Duration="0">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Collapsed</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </vsm:VisualState>
                        </vsm:VisualStateGroup>
                        <vsm:VisualStateGroup x:Name="InvertedStates">
                            <vsm:VisualState x:Name="InvertedItemsFlowDirection">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames 
                                        Storyboard.TargetName="ArrowCheckedToTop" 
                                        Storyboard.TargetProperty="Visibility" 
                                        Duration="0">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Visible</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames 
                                        Storyboard.TargetName="ArrowCheckedToBottom" 
                                        Storyboard.TargetProperty="Visibility" 
                                        Duration="0">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Collapsed</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </vsm:VisualState>
                            <vsm:VisualState x:Name="NormalItemsFlowDirection"/>
                        </vsm:VisualStateGroup>
                        <vsm:VisualStateGroup x:Name="ValidStates">
                            <vsm:VisualState x:Name="Valid"/>
                            <vsm:VisualState x:Name="NotValid">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames 
                                        Storyboard.TargetName="ValidElement" 
                                        Storyboard.TargetProperty="Visibility" 
                                        Duration="0">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Visible</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </vsm:VisualState>
                        </vsm:VisualStateGroup>
                        <vsm:VisualStateGroup x:Name="NodeLevelActionStates">
                            <vsm:VisualState x:Name="NormalLevelNode"/>
                            <vsm:VisualState x:Name="JumpLevelNode">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames 
                                        Storyboard.TargetName="NodeSpacerRectangle" 
                                        Storyboard.TargetProperty="Visibility" 
                                        Duration="0">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Visible</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </vsm:VisualState>
                        </vsm:VisualStateGroup>
                    </vsm:VisualStateManager.VisualStateGroups>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="*"/>
                        <RowDefinition Height="Auto"/>
                    </Grid.RowDefinitions>
                    <Rectangle x:Name="NodeSpacerRectangle" 
                       Height="6" Grid.Row="0" Visibility="Collapsed"/>
                    <StackPanel Orientation="Horizontal" Grid.Row="1">
                        <Rectangle Width="{TemplateBinding FullIndentation}" />
                        <Grid MinWidth="22" Margin="0,0,1,0">
                            <Grid x:Name="HasItem" 
                                  Visibility="Collapsed" 
                                  Height="16" Width="16" 
                                  Margin="0,0,0,0"
                                  VerticalAlignment="Bottom">
                                <Path x:Name="ArrowUnchecked" 
                                  HorizontalAlignment="Right" 
                                  Height="8" Width="8" 
                                  Fill="{StaticResource DefaultForeground}" 
                                  Stretch="Fill" 
                                  Data="M 4 0 L 8 4 L 4 8 Z" />
                                <Grid x:Name="CheckedArrow" Visibility="Collapsed">
                                    <Path x:Name="ArrowCheckedToTop" 
                                      HorizontalAlignment="Right" 
                                      Height="8" Width="8" 
                                      Fill="{StaticResource DefaultForeground}" 
                                      Stretch="Fill" 
                                      Data="M 8 4 L 0 4 L 4 0 z" 
                                      Visibility="Collapsed"/>
                                    <Path x:Name="ArrowCheckedToBottom" 
                                      HorizontalAlignment="Right" 
                                      Height="8" Width="8" 
                                      Fill="{StaticResource DefaultForeground}" 
                                      Stretch="Fill" 
                                      Data="M 0 4 L 8 4 L 4 8 Z" />
                                </Grid>
                                <ToggleButton 
                                  x:Name="ELEMENT_ExpandButton" 
                                  Height="16" Width="16"  
                                  Style="{StaticResource EmptyToggleButtonStyle}" 
                                  IsChecked="{TemplateBinding IsExpanded}" 
                                  IsThreeState="False" IsTabStop="False"/>
                            </Grid>
                        </Grid>
                        <g:GDockPanel Background="Transparent">
                            <o:ItemTitle g:GDockPanel.Dock="Top" 
                               Visibility="Collapsed" x:Name="ItemTitle" />
                            <Grid g:GDockPanel.Dock="Fill">
                                <Border x:Name="BackgroundVisual" 
                                    Background="{TemplateBinding Background}" />
                                <Rectangle 
                                   Fill="{StaticResource 
                                          DefaultAlternativeBackground}" 
                                   x:Name="AlternateBackgroundVisual" 
                                   Visibility="Collapsed"/>
                                <Grid x:Name="SelectedVisual" 
                                        Visibility="Collapsed" >
                                    <Grid.RowDefinitions>
                                        <RowDefinition Height="1*"/>
                                        <RowDefinition Height="1*"/>
                                    </Grid.RowDefinitions>
                                    <Rectangle Fill="{StaticResource DefaultDownColor}" 
                                           Grid.RowSpan="2"/>
                                    <Rectangle 
                                           x:Name="SelectedReflectVisual" 
                                           Fill="{StaticResource DefaultReflectVertical}" 
                                           Margin="0,1,1,0" RadiusX="1" 
                                           RadiusY="1"/>
                                </Grid>
                                <Rectangle 
                                   x:Name="MouseOverVisual" 
                                   Fill="{StaticResource 
                                          DefaultDarkGradientBottomVertical}" 
                                   Visibility="Collapsed" Margin="0,0,1,0"/>
                                <Grid x:Name="PressedVisual" 
                                        Visibility="Collapsed">
                                    <Grid.RowDefinitions>
                                        <RowDefinition Height="1*"/>
                                        <RowDefinition Height="1*"/>
                                    </Grid.RowDefinitions>
                                    <Rectangle 
                                            Fill="{StaticResource DefaultDownColor}" 
                                            Grid.RowSpan="2"/>
                                    <Rectangle 
                                        Fill="{StaticResource 
                                               DefaultDarkGradientBottomVertical}" 
                                        Grid.Row="1" Margin="0,0,1,0"/>
                                    <Rectangle 
                                        Fill="{StaticResource DefaultReflectVertical}" 
                                        Margin="0,1,1,0" 
                                        RadiusX="1" RadiusY="1"/>
                                </Grid>
                                <Rectangle 
                                       HorizontalAlignment="Stretch" 
                                       VerticalAlignment="Top" 
                                       Stroke="{TemplateBinding BorderBrush}" 
                                       StrokeThickness="0.5" 
                                       Height="1"/>
                                <Rectangle 
                                   Name="ValidElement" 
                                   Stroke="Red" 
                                   StrokeThickness="1.5"
                                   IsHitTestVisible="false" 
                                   Visibility="Collapsed"
                                   Margin="1,2,2,1"/>
                                <Rectangle 
                                   x:Name="FocusVisual" 
                                   Stroke="{StaticResource DefaultFocus}" 
                                   StrokeDashCap="Round" Margin="1,2,2,1" 
                                   StrokeDashArray=".2 2" 
                                   Visibility="Collapsed"/>
                                <g:GContentPresenter
                                          x:Name="ELEMENT_ContentPresenter"
                                          Content="{TemplateBinding Content}"
                                          ContentTemplate=
                                            "{TemplateBinding ContentTemplate}"
                                          Cursor="{TemplateBinding Cursor}"
                                          OrientatedHorizontalAlignment=
                                            "{TemplateBinding HorizontalContentAlignment}"
                                          OrientatedMargin="{TemplateBinding Padding}"
                                          OrientatedVerticalAlignment=
                                            "{TemplateBinding VerticalContentAlignment}" 
                                          PresenterOrientation=
                                            "{TemplateBinding PresenterOrientation}"/>
                                <Rectangle 
                                       x:Name="BorderElement" 
                                       Stroke="{TemplateBinding BorderBrush}" 
                                       StrokeThickness=
                                           "{TemplateBinding BorderThickness}" 
                                       Margin="-1,0,0,-1"/>
                            </Grid>
                        </g:GDockPanel>
                    </StackPanel>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

向HandyContainer添加ItemTitleSource和ItemTitleStartDepth属性

HandyContainer(即我们的网格主体)需要知道在ItemTitle的内容中放置什么。这将是我们添加到HandyContainer类中的ItemTitleSource属性的目的。

大多数情况下,我们可能不希望ItemTitle显示在所有层次级别(通常不显示在根级别)。因此,我们还将添加ItemTitleStartDepth属性。此属性将告诉网格主体从哪个级别开始显示项的标题。在下图 below,标题显示在层次结构的第一个和第二个级别,但未在根级别显示。

Container_RowNodeStyle With ItemTitle

让我们将这两个依赖项属性添加到我们的HandyContainer部分类中。编辑位于GoaOpen\Extensions\Grid文件夹中的HandyContainer.cs文件。在文件开头添加两个依赖项属性:

public partial class HandyContainer : HandyListControl
{
    public static readonly DependencyProperty ItemTitleSourceProperty = 
      DependencyProperty.Register("ItemTitleSource", 
      typeof(string), typeof(HandyContainer), null);
    public static readonly DependencyProperty ItemTitleStartDepthProperty = 
      DependencyProperty.Register("ItemTitleStartDepth", typeof(int), 
      typeof(HandyContainer), new PropertyMetadata(1));
    
    public event EventHandler CurrentCellNameChanged;
...

默认情况下,ItemTitleStartDepth值为“1”。这意味着ItemTitle将从第一个级别(而不是根级别)开始显示。

让我们还将相应的属性setter和getter添加到我们的类中:

public string ItemTitleSource
{
    get
    {
        return (string)this.GetValue(ItemTitleSourceProperty);
    }
    set
    {
        base.SetValue(ItemTitleSourceProperty, value);
    }
}

public int ItemTitleStartDepth
{
    get
    {
        return (int)this.GetValue(ItemTitleStartDepthProperty);
    }
    set
    {
        base.SetValue(ItemTitleStartDepthProperty, value);
    }
}

在我们的GridBody项目中,让我们修改Person类,并向其添加一个Title属性。此属性将用作ItemTitleSource

public string Title
{
    get { return "Persons"; }
}

让我们对Country类做同样的处理……

public string Title
{
    get { return "Countries"; }
}

……以及StateProvince类:

public string Title
{
    get { return "Regions"; }
}

让我们还修改GridBody项目的Page.xaml文件,并为HandyContainerItemTitleSource属性提供一个值:

<o:HandyContainer
   x:Name="MyGridBody"
   VirtualMode="On"
   AlternateType="Items"
   HandyDefaultItemStyle="Node"
   HandyStyle="GridBodyStyle"
   CurrentCellValidating="MyGridBody_CurrentCellValidating"
   CurrentItemValidating="MyGridBody_CurrentItemValidating"
   ItemTitleSource="Title"
   g:GDockPanel.Dock="Fill">

引用ItemTitle并使其可见

我们需要在ContainerItem类的OnApplyTemplate方法中引用ItemTitle控件。

让我们编辑位于GoaOpen\Extensions\Grid文件夹中的ContainerItem部分类,并修改OnApplyTemplate方法:

private ItemTitle itemTitle;
public override void OnApplyTemplate()
{
    cellCollection = null;

    _OnApplyTemplate();
    base.OnApplyTemplate();

    itemTitle = this.GetTemplateChild("ItemTitle") as ItemTitle;

    ApplyHeadingVisibility();
}

ApplyHeadingVisibility方法将根据项的NodeLevelAction状态和父HandyContainerItemTitleStartDepth属性值来决定ItemTitle的可见性。ApplyHeadingVisibility方法将调用一个名为ApplyTitleContent的方法,该方法将根据父HandyContainerItemTitleSource值填充ItemTitle的内容。

为了让这两个方法正常工作,我们需要在ContainerItem文件顶部添加两个“using”子句:

using System.Reflection;
using Netika.Windows.Controls;

internal virtual void ApplyHeadingVisibility()
{
    if (itemTitle == null)
        return;

    Visibility headingVisibility = Visibility.Collapsed;
    if (this.NodeLevelAction == NodeLevelAction.LevelJump)
        headingVisibility = Visibility.Visible;

    HandyContainer parentContainer = 
       HandyContainer.GetParentContainer(this);
    int parentCount = this.ParentCount;

    Visibility titleVisibility = Visibility.Collapsed;
    if ((parentContainer.ItemTitleStartDepth >= 0) && 
        (parentCount > parentContainer.ItemTitleStartDepth))
        titleVisibility = headingVisibility;

    if ((itemTitle != null) && (itemTitle.Visibility != titleVisibility))
    {
        if (titleVisibility == Visibility.Visible)
            ApplyTitleContent();

        itemTitle.Visibility = titleVisibility;
    }
    
}

private void ApplyTitleContent()
{
    if (itemTitle != null)
    {
        HandyContainer parentContainer = HandyContainer.GetParentContainer(this);
        if (!string.IsNullOrEmpty(parentContainer.ItemTitleSource))
        {
            PropertyInfo propInfo = 
              this.DataContext.GetType().GetProperty(parentContainer.ItemTitleSource);
            if (propInfo != null)
                itemTitle.Content = propInfo.GetValue(this.DataContext, null);
        }
    }
}

让我们尝试通过启动GridBody项目来测试我们的更改。ItemTitle显示在第一个人员项的顶部,以及层次结构每个级别的第一个区域的顶部。现在,让我们尝试滚动网格。滚动时,ItemTitle显示得不规律。有时它们显示在错误的位置。其他时候,它们根本不显示。发生了什么?

重写OnNodeLevelActionChanged和OnRefreshed方法

我们的GridBody使用虚拟模式。为了尽可能快地保持网格的性能,只有显示的项(以及稍多一些)被创建在Visual Tree中。此外,当网格滚动时,不会销毁现有项并创建新项,而是尽可能多地重复使用现有项(创建新控件并将其添加到Visual Tree是一个非常耗时的过程)。

当项被重复使用时,该项的OnApplyTemplate不再被调用。因此,ApplyHeadingVisibility方法不会在这些项上调用。这就是为什么一旦滚动网格,项的标题就不会显示在正确的位置。

为了使ItemTitle正确显示,我们必须重写ContainerItemOnNodeLevelActionChangedOnRefreshed方法。

OnNodeLevelActionChanged方法在项的NodeLevelAction状态发生变化时被调用。OnRefresh方法在ContainerItem被重用并绑定到ItemsSource的另一个元素时被调用。

必须在每次调用这些方法之一时调用ApplyHeadingVisibility方法。

protected override void OnNodeLevelActionChanged(EventArgs e)
{
    base.OnNodeLevelActionChanged(e);

    ApplyHeadingVisibility();
}

protected override void OnRefreshed(EventArgs e)
{
    base.OnRefreshed(e);

    ApplyHeadingVisibility();
}

此外,我们必须修改ApplyHeadingVisibility以考虑到项可能被重用。假设一个项显示一种元素,并且其NodeLevelAction状态为LevelJump。用户滚动网格,该项被重用,但这次用于显示另一种元素。当它再次使用时,该项位于其NodeLevelAction状态为LevelJump的位置。由于ItemTitle在项被重用之前已经可见,因此ApplyTitleContent方法不会在ApplyHeadingVisiblity中调用,并且ItemTitle的内容不会刷新。因此,ItemTitle显示了错误的标题!

为了纠正这一点,让我们删除ApplyHeadingVisibility方法中if语句中的第二个条件。

if ((itemTitle != null) && (itemTitle.Visibility != titleVisibility))

if (itemTitle != null)
{
    if (titleVisibility == Visibility.Visible)
        ApplyTitleContent();

    itemTitle.Visibility = titleVisibility;
}

如果我们再次启动应用程序并滚动网格,ItemTitle将显示在正确的位置。

动态ItemTitleSource或ItemTitleStartDepth

ItemTitleSourceItemTitleStartDepth的实现方式是,当它们的值被修改时,网格主体内显示的标题不会自动更新。为了实现此功能,我们应该跟踪ItemTitleSourceItemTitleStartDepth值的更改。这可以在我们网格的增强版本中实现。

ItemTitle点击

如果我们尝试单击一个项标题,我们可以看到包含项标题的项被选中。这不是我们想要的行为。单击ItemTitle不应对包含它的项产生任何影响。

让我们修改ItemTitle类并重写OnMouseLeftButtonDown方法以删除此不期望的行为。

public class ItemTitle : GContentControl
{
    public ItemTitle()
    {
        this.DefaultStyleKey = typeof(ItemTitle);
    }

    protected override void OnMouseLeftButtonDown(
              System.Windows.Input.MouseButtonEventArgs e)
    {
        e.Handled = true;

        base.OnMouseLeftButtonDown(e);
    }
}

ItemTitle鼠标悬停

如果我们鼠标指针移到项标题上,我们可以看到包含该项标题的项的背景以交替颜色显示。这是因为ItemTitle是包含它的项的一部分。

让我们捕获ItemTitleMouseEnterMouseLeave事件以消除此行为。

让我们首先修改ContainerItemOnApplyTemplate方法。

private ItemTitle itemTitle;
public override void OnApplyTemplate()
{
    cellCollection = null;

    _OnApplyTemplate();
    base.OnApplyTemplate();

    if (itemTitle != null)
    {
        itemTitle.MouseEnter -= new MouseEventHandler(headers_MouseEnter);
        itemTitle.MouseLeave -= new MouseEventHandler(headers_MouseLeave);
    }
    itemTitle = this.GetTemplateChild("ItemTitle") as ItemTitle;
    if (itemTitle != null)
    {
        itemTitle.MouseEnter += new MouseEventHandler(headers_MouseEnter);
        itemTitle.MouseLeave += new MouseEventHandler(headers_MouseLeave);
    }
   
    ApplyHeadingVisibility();
}

让我们实现headers_MouseEnterheaders_MouseLeave方法。

void headers_MouseLeave(object sender, MouseEventArgs e)
{
    GoToState("NotMouseOverHeading", true);
}

void headers_MouseEnter(object sender, MouseEventArgs e)
{
    GoToState("MouseOverHeading", true);
}

我们为ContainerItem添加了另外两个状态:NotMouseOverHeading状态和MouseOverHeading状态。我们现在需要修改ContainerItem样式以考虑这两个新状态。

generic.xaml文件中找到Container_RowNodeStyle。在样式的VisualStateManager.VisualStateGroups末尾添加以下VisualStateGroup

<vsm:VisualStateGroup x:Name="MouseOverHeadingStates">
    <vsm:VisualState x:Name="NotMouseOverHeading"/>
    <vsm:VisualState x:Name="MouseOverHeading">
        <Storyboard>
            <DoubleAnimation 
                Duration="0" 
                Storyboard.TargetName="MouseOverVisual" 
                Storyboard.TargetProperty="Opacity" To="0"/>
        </Storyboard>
    </vsm:VisualState>
</vsm:VisualStateGroup>

如果我们再次启动应用程序,并将鼠标移到ItemTitle上,则包含标题的项的背景将保持不变。

4. 主体标题

引言

我们将“主体标题”称为显示在网格主体内部的标题。显示在网格顶部的标题将称为“顶部标题”。

创建网格的过程可分为三个步骤:

  1. HandyContainer添加到我们的XAML页面并相应设置其属性。
  2. 编写HandyContainerItemTemplate,以便选择行内单元格(即项)的显示方式。
  3. 填充HandyContainerItemsSource属性。

我们的要求是:我们不想添加单独的步骤来定义主体标题。标题的布局方式必须与行单元格相同。

例如,如果HandyContainerItemTemplate显示网格行如下:

Cells' Location

那么标题必须以同样的方式显示:

Headers' Location

因此,用于构建网格行的ItemTemplate也将用于构建标题。

更新Cell类

引言

附加到GridBodyHandyContainerItemTemplate将用于构建行和标题。因此,在定义HandyContainerItemTemplate时,必须能够定义标题的一些行为。

为了实现此功能,我们将向Cell类添加两个属性:

  • Header:此属性允许定义附加到单元格的标题的内容。换句话说,它允许定义在标题内显示什么(文本、图像等)。
  • UserResizeType:用户调整标题大小(因此也调整单元格大小)的方式。可能的值为NoneRightBottom

创建HeaderResizeTypes枚举

让我们创建枚举,它将允许填充Cell类的UserResizeType属性。在GoaOpen\Extensions\Grid文件夹中,创建新的HeaderResizeType文件。

using System;

namespace Open.Windows.Controls
{
    [Flags]
    public enum HeaderResizeTypes
    {
        None = 0,
        Right = 1,
        Bottom = 2
    }
}

枚举表示的选项可以组合在一起,以实现一个可以同时进行右侧调整和底部调整的标题。因此,我们为枚举应用了“Flags”属性。

现在让我们添加Cell类的HeaderUserResizeType属性:

public abstract class Cell : Control
{
    public static readonly DependencyProperty CanEditProperty;
    public static readonly DependencyProperty UserResizeTypeProperty;
    public static readonly DependencyProperty HeaderProperty;

    static Cell()
    {
        CanEditProperty = DependencyProperty.Register("CanEdit", 
                          typeof(bool), typeof(Cell), new PropertyMetadata(true));
        UserResizeTypeProperty = DependencyProperty.Register("UserResizeType", 
                                 typeof(HeaderResizeTypes), typeof(Cell), 
                                 new PropertyMetadata(HeaderResizeTypes.Right));
        HeaderProperty = DependencyProperty.Register(
           "Header", typeof(object), typeof(Cell), null);
        
    }

    public HeaderResizeTypes UserResizeType
    {
        get { return (HeaderResizeTypes)GetValue(UserResizeTypeProperty); }
        set { SetValue(UserResizeTypeProperty, value); }
    }

    public object Header
    {
        get { return GetValue(HeaderProperty); }
        set { SetValue(HeaderProperty, value); }
    }

创建并显示HeadersContainer控件

引言

“网格标题”将由一个HeadersContainer组成,该控件持有标题。

Alternate Type

让我们首先创建一个空的HeadersContainer,并将其显示在网格主体内的正确位置。

HeadersContainer类

GoaOpen\Extensions\Grid文件夹中,让我们添加HeadersContainer类。

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using Netika.Windows.Controls;

namespace Open.Windows.Controls
{
    public class HeadersContainer : GContentControl
    {
        public HeadersContainer()
        {
            this.DefaultStyleKey = typeof(HeadersContainer);
        }
    }
}

HeadersContainerContentControl。我们选择继承自ContentControl,因为稍后我们将把HandyContainerItemTemplate应用于HeadersContainer控件的ContentTemplate。这将允许我们对标题应用与应用于单元格相同的布局。

目前,让我们只保持HeadersContainer非常简单。

HeadersContainer样式

我们需要为HeadersContainer定义一个样式。

在此阶段,我们将做一个非常简单的。打开GoaOpen项目的generic.xaml文件,导航到文件末尾。添加以下样式:

<Style TargetType="o:HeadersContainer">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="o:HeadersContainer">
                <Grid  x:Name="LayoutRoot">
                    <g:GContentPresenter
                            x:Name="ELEMENT_ContentPresenter"/>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

在GridBody中显示HeadersContainer

引言

HeadersContainer将以与ItemTitle相同的方式显示,并且紧随其下方。由于我们已经完成了在网格主体内显示ItemTitle所需的所有必要步骤,因此显示标题非常容易。我们只需重复相同的步骤。对吗?

不对!!!!

ItemTitle是一个非常简单的控件,只需要少量时间即可布局和显示。相反,HeadersContainer要复杂得多。它需要一个DataTemplate才能正确布局。它还将包含几个标题子控件。由于标题可以调整大小(不总是),它将在其中包含一个WindowSizer。因此,标题将是难以布局和显示的控件。

我们必须考虑到HeadersContainer渲染速度慢的事实,并努力保持数据网格尽可能快,尤其是在用户滚动时。Silverlight有很多出色的功能,但其弱点之一是渲染和布局VisualTree时速度非常慢。我们在设计新控件时必须牢记这一点。

当我们说布局和渲染过程非常慢时,这并不意味着该过程本身需要几秒钟才能完成。这意味着如果同时处理多个此类操作,整个过程可能需要太长时间,并且用户与用户界面的交互将不再流畅。

到目前为止,我们还没有真正处理过这个问题,因为HandyContainer已经处理了。

让我们举个例子。假设我们有一个应用程序显示以下网格:

Header Recreate 1

假设用户按下“page-down”键并显示以下行:

Header Recreate 2

如果我们不注意我们的操作,在上述情况下,当用户按下“page-down”键时,将创建、布局和渲染六个新的标题。在其他情况下,情况可能会更糟。这可能会使滚动过程比现在慢得多。

避免此问题的唯一方法是保持标题在Visual Tree中可见,一旦创建它们,并尽可能多地重复使用它们。

为了做到这一点,我们将在GridBody内部添加一个Canvas。我们将我们的HeadersContainer添加到该画布中,并尽可能多地重复使用它。

Canvas是我们需要的面板,因为它允许我们精确管理HeadersContainer的显示位置。将HeadersContainer从画布的一个位置移动到另一个位置并不意味着HeadersContainer将重新布局(只要我们保持相同的高度和相同的宽度)。这是最重要的特性。在我们的例子中,画布也将用作缓存。一旦HeadersContainer不再需要,它将被移动到一个用户不可见的位置(例如,(-1000, -1000))。这样,即使HeadersContainer不再可见,它仍然是Visual Tree的一部分。一旦我们需要缓存的HeadersContainer,我们将其移回画布的可视区域。此过程不需要重新创建或重新布局HeadersContainer

向HandyContainer添加Canvas

让我们修改GridBody样式,并将包含HeaderContainer的画布添加到其中。

generic.xaml文件中找到GridBodyStyle样式。在Control模板中找到Scroller,并用以下内容替换它:

<g:Scroller 
    x:Name="ElementScroller"
    Style="{TemplateBinding ScrollerStyle}" 
    Background="Transparent"
    BorderThickness="0"
    Margin="{TemplateBinding Padding}"
    ScrollerOperator="ELEMENT_ItemsPresenter">
    <Grid>
        <g:GItemsPresenter
            x:Name="ELEMENT_ItemsPresenter"
            Opacity="{TemplateBinding Opacity}"
            Cursor="{TemplateBinding Cursor}"
            HorizontalAlignment ="{TemplateBinding HorizontalContentAlignment}"
            VerticalAlignment ="{TemplateBinding VerticalContentAlignment}"/>
        <Grid Margin="-1,0,0,0">
            <g:GCanvas x:Name="HeadersCanvas" 
                        Margin="{TemplateBinding Padding}"/>
        </Grid>
    </Grid>
</g:Scroller>

我们在Scroller内部添加了一个名为HeadersCanvasGCanvas

Scroller是一个允许滚动Panel内容的控件。Scroller(GOA控件)和ScrollViewer(Silverlight控件)的区别在于,scroller允许滚动实现IScrollerOperator接口的面板内容。如果我们将一个面板放在ScrollViewer中,当用户滚动时,面板会相应移动。如果我们将其放在Scroller中,当用户滚动时,面板不会移动,但会告诉面板移动其内容。我们在这里不深入细节,只说明Scroller被用作ScrollViewer的替代品,主要是因为它能够支持虚拟模式。

由于我们在Scroller中添加了HeadersCanvas,因此Scroller现在包含多个面板。因此,我们必须告诉Scroller当用户使用控件的滚动条时,它将滚动哪个面板。我们通过将ScrollerScrollOperator属性填充为GItemPresenter的名称来做到这一点。

注意:GItemsPresenter不是面板。尽管如此,它的目的是“成为”HandyContainerItemHost。它将被HandyContainerItemsPanelModel属性中定义的面板实例“替换”。因此,Scroller会将GItemPresenter视为面板。

实现HeadersContainer缓存

我们将向HandyContainer添加两个方法:GetHeadersContainerCacheHeadersContainer

GetHeadersContainer方法允许获取一个“空闲”的HeadersContainer。空闲的HeadersContainer是一个当前未使用的(未显示的)HeadersContainer。该方法将获取一个空闲的HeadersContainer,要么从缓存中获取,要么在缓存为空时创建一个新的。

CacheHeadersContainer方法将缓存一个不再使用的HeadersContainer

所有HeadersContainer都不包含相同的标题。例如,如果我们的网格显示以下层次结构:

Countries
    Regions
        Employees

那么国家标题将与人员标题不同。

我们将假定HeadersContainer可以被重用:

  • 如果链接到项的元素的类型与创建HeadersContainer时链接到项的元素的类型相同。换句话说,如果一个HeadersContainer链接到显示人员数据的项,它就可以成为链接到显示国家数据的其他项的HeadersContainer。相反,它不能成为链接到显示国家数据的项的HeadersContainer(否则,HeadersContainer需要重建,这个过程将需要很长时间)。
  • 如果链接到标题的项与创建HeadersContainer时链接到项的项在相同的层次结构级别。

没有图片很难理解。让我们看看下面的两张图片:

Headers Reuse 1

Headers Reuse 2

第一张图片中用红色矩形圈出的标题可以重用来显示第二张图片中用红色矩形圈出的标题。

相反,第一张图片中用绿色矩形圈出的标题不能用来显示第二张图片中用红色矩形圈出的标题。

HandyContainer类中,让我们首先通过修改OnApplyTemplate方法来添加对HeadersCanvas的引用:

private Control lastFocusControl;
private GCanvas headersCanvas;
public override void OnApplyTemplate()
{
    base.OnApplyTemplate();

    lastFocusControl = this.GetTemplateChild("LastFocusControl") as Control;
    headersCanvas = this.GetTemplateChild("HeadersCanvas") as GCanvas;
}

然后,让我们将以下代码添加到我们的HandyContainer部分类中:

private class HeadersContainerCache
{
    public HeadersContainerCache(HeadersContainer headersContainer, 
           Type dataType, int level)
    {
        HeadersContainer = headersContainer;
        DataType = dataType;
        Level = level;
    }

    public HeadersContainer HeadersContainer
    {
        get;
        private set;
    }

    public Type DataType
    {
        get;
        private set;
    }

    public int Level
    {
        get;
        private set;
    }
}

private List<HeadersContainerCache> headersContainerCacheCollection;
internal HeadersContainer GetHeadersContainer(Type dataType, 
                          int level, ContainerItem item)
{
    if (headersContainerCacheCollection == null)
        headersContainerCacheCollection = new List<HeadersContainerCache>();

    HeadersContainerCache resultCache = null;
    foreach (HeadersContainerCache headersContainerCache in 
             headersContainerCacheCollection)
    {
        if ((headersContainerCache.DataType == dataType) && 
            (headersContainerCache.Level == level))
        {
            resultCache = headersContainerCache;
            break;
        }
    }

    HeadersContainer result = null;
    if (resultCache != null)
    {
        headersContainerCacheCollection.Remove(resultCache);
        result = resultCache.HeadersContainer;
    }
    else
    {
        result = new HeadersContainer();
        
        if (headersCanvas != null)
            headersCanvas.Children.Add(result);
    }

    return result;
}

internal void CacheHeadersContainer(Type dataType, int level, 
              HeadersContainer headersContainer)
{
    headersContainer.SetValue(GCanvas.TopProperty, -1000.0);
    headersContainerCacheCollection.Add(
      new HeadersContainerCache(headersContainer, dataType, level));
}

headersContainerCacheCollection包含所有已缓存的HeadersContainer

当一个HeadersContainer被缓存时(通过CacheHeadersContainer方法完成),它不会从HeadersCanvas中删除。HeadersContainer的引用被添加到headersContainerCacheCollection,并且HeadersContainer被移动到画布上一个用户不可见的位置(Top = -1000)。

当需要一个headerContainer时,会调用GetHeadersContainer方法。如果headersContainerCacheCollection中存在合适的HeadersContainer(数据类型相同且层次结构级别相同),则返回该headersContainer。否则,将创建一个新的HeadersContainer

现在我们已经实现了标题缓存,我们可以按照实现ItemTitle时几乎相同的步骤来实现标题的显示。

Container_RowNodeStyle中的Headers

HeadersContainer将不由ContainerItem持有,而是由HandyContainerHeadersCanvas持有。尽管如此,我们仍然需要修改Container_RowNodeStyle,以便在HeadersContainer显示的位置留出一个空位。

Alternate Type

为了能够构建这个空位,让我们在Container_RowNodeStyle中,在ItemTitle下方添加一个网格。在generic.xaml文件中找到Container_RowNodeStyle。在ControlTemplate中找到ItemTitle。在ItemTitle下方添加一个名为Headers的网格:

<g:GDockPanel Background="Transparent">
    <o:ItemTitle g:GDockPanel.Dock="Top" 
       Visibility="Collapsed" x:Name="ItemTitle" />
    <Grid g:GDockPanel.Dock="Top" 
       Visibility="Collapsed" x:Name="Headers"/>
    <Grid g:GDockPanel.Dock="Fill">

HeadersStartDepth属性

就像我们向HandyContainer添加了ItemTitleStartDepth属性一样,我们需要添加一个HeadersStartDepth属性。此属性将告诉网格主体从哪个级别开始显示项的标题。

编辑位于GoaOpen\Extensions\Grid文件夹中的HandyContainer.cs文件。添加HeadersStartDepth属性:

public partial class HandyContainer : HandyListControl
{
    public static readonly DependencyProperty ItemTitleSourceProperty = 
      DependencyProperty.Register("ItemTitleSource", typeof(string), 
      typeof(HandyContainer), null);
    public static readonly DependencyProperty ItemTitleStartDepthProperty = 
      DependencyProperty.Register("ItemTitleStartDepth", typeof(int), 
      typeof(HandyContainer), new PropertyMetadata(1));
    public static readonly DependencyProperty HeadersStartDepthProperty = 
      DependencyProperty.Register("HeadersStartDepth", 
      typeof(int), typeof(HandyContainer), new PropertyMetadata(1));

默认情况下,ItemTitleStartDepth值为“1”。这意味着标题将从第一个级别(而不是根级别)开始显示。

让我们还将相应的属性setter和getter添加到类中:

public int HeadersStartDepth
{
    get
    {
        return (int)this.GetValue(HeadersStartDepthProperty);
    }
    set
    {
        base.SetValue(HeadersStartDepthProperty, value);
    }
}

修改ContainerItem类

让我们修改ContainerItem类,以便它能够管理它链接到的HeadersContainer(如果有)。

让我们首先修改OnApplyTemplate方法,以考虑标题面板:

private Panel headers;
private ItemTitle itemTitle;
public override void OnApplyTemplate()
{
    cellCollection = null;

    _OnApplyTemplate();
    base.OnApplyTemplate();

    if (itemTitle != null)
    {
        itemTitle.MouseEnter -= new MouseEventHandler(headers_MouseEnter);
        itemTitle.MouseLeave -= new MouseEventHandler(headers_MouseLeave);
    }
    itemTitle = this.GetTemplateChild("ItemTitle") as ItemTitle;
    if (itemTitle != null)
    {
        itemTitle.MouseEnter += new MouseEventHandler(headers_MouseEnter);
        itemTitle.MouseLeave += new MouseEventHandler(headers_MouseLeave);
    }

    if (headers != null)
    {
        headers.MouseEnter -= new MouseEventHandler(headers_MouseEnter);
        headers.MouseLeave -= new MouseEventHandler(headers_MouseLeave);
    }

    headers = this.GetTemplateChild("Headers") as Panel;

    if (headers != null)
    {
        headers.MouseEnter += new MouseEventHandler(headers_MouseEnter);
        headers.MouseLeave += new MouseEventHandler(headers_MouseLeave);
    }

    ApplyHeadingVisibility();
}

让我们还修改ApplyHeadingVisibility方法以考虑标题:

internal virtual void ApplyHeadingVisibility()
{
    if ((itemTitle == null) && (headers == null))
        return;

    Visibility headingVisibility = Visibility.Collapsed;
    if (this.NodeLevelAction == NodeLevelAction.LevelJump)
        headingVisibility = Visibility.Visible;

    HandyContainer parentContainer = HandyContainer.GetParentContainer(this);
    int parentCount = this.ParentCount;

    Visibility titleVisibility = Visibility.Collapsed;
    if ((parentContainer.ItemTitleStartDepth >= 0) && 
            (parentCount > parentContainer.ItemTitleStartDepth))
        titleVisibility = headingVisibility;

    if (itemTitle != null)
    {
        if (titleVisibility == Visibility.Visible)
            ApplyTitleContent();

        itemTitle.Visibility = titleVisibility;
    }

    Visibility headersVisibility = Visibility.Collapsed;
    if ((parentContainer.HeadersStartDepth >= 0) && 
              (parentCount > parentContainer.HeadersStartDepth))
    {
        headersVisibility = headingVisibility;
    }

    if (headers != null)
    {
        if (headersVisibility == Visibility.Visible)
        {
            InitializeHeadersContainer();
            headers.Visibility = Visibility.Visible;

        }
        else
        {
            if (parentContainer != null)
                RemoveHeadersContainer(parentContainer);

            headers.Visibility = Visibility.Collapsed;
        }
    }
}

HeadersContainer headersContainer = null;
private void InitializeHeadersContainer()
{
    HandyContainer handyContainer = HandyContainer.GetParentContainer(this);

    if (headersContainer != null)
    {
        if ((headersContainer.Content.GetType() == this.Content.GetType()) &&
            (headersContainer.SourceLevel == this.ParentCount))
            return;
        else
            RemoveHeadersContainer(handyContainer);
    }

    if (handyContainer != null)
    {
        headersContainer = handyContainer.GetHeadersContainer(
                            this.Content.GetType(), this.ParentCount, this);

        headersContainer.Content = "This is the header";
        headersContainer.Width = this.ActualWidth;
        headers.Height = headersContainer.ActualHeight;
        headersContainer.SizeChanged += 
           new SizeChangedEventHandler(headersContainer_SizeChanged);
    }

}

void headersContainer_SizeChanged(object sender, SizeChangedEventArgs e)
{
    if (headers != null)
        headers.Height = e.NewSize.Height;
}

protected override void OnArranged(Rect rect)
{
    base.OnArranged(rect);

    if (headersContainer != null)
    {
        if (headers != null)
        {
            double top = 0;
            DependencyObject parentElement = headers;
            while (parentElement != null)
            {
                FrameworkElement frameworkElement = 
                           parentElement as FrameworkElement;
                if (frameworkElement != null)
                {
                    if (frameworkElement == this)
                        break;

                    top += System.Windows.Controls.Primitives.
                             LayoutInformation.GetLayoutSlot(frameworkElement).Top;
                }

                parentElement = VisualTreeHelper.GetParent(parentElement);
            }

            top += rect.Top;
            GCanvas.SetTop(headersContainer, top);
        }
    }
}

protected override Size ArrangeOverride(Size finalSize)
{
    Size result = base.ArrangeOverride(finalSize);

    if (headersContainer != null)
        headersContainer.Width = result.Width;

    return result;
}


internal void RemoveHeadersContainer(HandyContainer handyContainer)
{
    if (headersContainer != null)
    {
        handyContainer.CacheHeadersContainer(this.Content.GetType(), 
                                             this.ParentCount, headersContainer);
        headers.Height = 0;
        headersContainer.SizeChanged -= 
          new SizeChangedEventHandler(headersContainer_SizeChanged);
        headersContainer = null;
    }
}

请注意,在ApplyHeadingVisibility方法中,当我们使用HeadersContainer时,我们调用InitializeHeadersContainer方法,当我们不再需要它时,我们调用RemoveHeadersContainer方法。

InitializeHeadersContainer方法调用父HandyContainerGetHeadersContainer方法以从缓存中获取HeadersContainerRemoveHeadersContainer方法调用父HandyContainerCacheHeadersContainer方法将HeadersContainer放回缓存。

HeadersContainer需要位于正确的位置,并且其宽度必须根据项的宽度设置。这在多个地方完成:

  • InitializeHeadersContainer中,我们初始化了HeadersContainer的位置和大小。
  • OnArrange方法中,我们计算了HeadersContainer的顶部位置。当项被HandyContainerItemsHost排列(移动或调整大小时)时,会调用OnArrange方法。
  • ArrangeOverride方法中。当Silverlight必须排列项时,它会调用ArrangeOverride方法。

当标题不再可见时,RemoveHeadersContainer方法由ApplyHeadingVisibility调用。

但是,从ContainerItem内部调用RemoveHeadersContainer还不够。如果一个ContainerItemItemsHost中删除,ContainerItem不知道此操作。但是,在这种情况下,我们也需要调用RemoveHeadersContainer方法。幸运的是,HandyContainer类有一个ClearContainerForItemOverride方法,该方法在每次发生此类事件时都会被调用。让我们重写HandyContainerClearContainerForItemOveride方法:

protected override void ClearContainerForItemOverride(
          DependencyObject element, object item)
{
    ((ContainerItem)element).RemoveHeadersContainer(this);

    base.ClearContainerForItemOverride(element, item);
}

InitializeHeadersContainer方法考虑到了ContainerItem可能被“重用”的事实(请参阅本教程以上关于此内容的说明)。如果HeadersContainer已存在并链接到正确类型的数据,并链接到正确的层次结构级别,则会重用它。否则,将创建一个新的。

为了检查现有HeadersContainer链接到的层次结构级别,InitializeHeadersContainer方法会检查HeadersContainerSourceLevel属性的值。

我们还没有创建这个属性。让我们将其添加到HeadersContainer类中:

public class HeadersContainer : GContentControl
{
    public static readonly DependencyProperty SourceLevelProperty;

    static HeadersContainer()
    {
        SourceLevelProperty = DependencyProperty.Register("SourceLevel", 
                   typeof(int), typeof(HeadersContainer), 
                   new PropertyMetadata(1));            
    }

    public HeadersContainer()
    {
        this.DefaultStyleKey = typeof(HeadersContainer);
    }

    public int SourceLevel
    {
        get { return (int)GetValue(SourceLevelProperty); }
        set { SetValue(SourceLevelProperty, value); }
    }
}

让我们在HandyContainerGetHeadersContainer方法中填充SourceLevel属性的值:

private List<HeadersContainerCache> headersContainerCacheCollection;
internal HeadersContainer GetHeadersContainer(Type dataType, 
                          int level, ContainerItem item)
{
    ...

    HeadersContainer result = null;
    if (resultCache != null)
    {
        headersContainerCacheCollection.Remove(resultCache);
        result = resultCache.HeadersContainer;
    }
    else
    {
        result = new HeadersContainer();
        result.SourceLevel = level;

        if (headersCanvas != null)
            headersCanvas.Children.Add(result);
    }

    return result;
}

为了能够观察HeadersContainers,我们暂时用“This is the header”字符串填充了它们的内容(请看InitializeHeadersContainer方法)。

让我们启动我们的应用程序。

标题显示在正确的位置。它们的缩进不正确,但这是因为我们还没有为HeadersContainer创建实际的样式来考虑缩进。我们将在下一步进行。

增强HeadersContainer样式

让我们在generic.xaml文件中为HeadersContainer添加一个增强样式。

<Style x:Key="HeadersContainerStyle" TargetType="o:HeadersContainer">
    <Setter Property="IsEnabled" Value="true" />
    <Setter Property="HorizontalContentAlignment" Value="Stretch" />
    <Setter Property="VerticalContentAlignment" Value="Stretch" />
    <Setter Property="BorderBrush" Value="{StaticResource DefaultListControlStroke}"/>
    <Setter Property="BorderThickness" Value="1"/>
    <Setter Property="Cursor" Value="Arrow" />
    <Setter Property="Background" 
            Value="{StaticResource DefaultDarkGradientBottomVertical}" />
    <Setter Property="Foreground" Value="{StaticResource DefaultForeground}"/>
    <Setter Property="FontSize" Value="11" />
    <Setter Property="PresenterOrientation" Value="Horizontal" />
    <Setter Property="IsTabStop" Value="False" />
    <Setter Property="Indentation" Value="30" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="o:HeadersContainer">
                <g:GCanvas Name="RootCanvas" Margin="0,0,-1,-1">
                    <Grid x:Name="LayoutRoot">
                        <g:GStackPanel Orientation="Horizontal">
                            <Rectangle Width="{TemplateBinding FullIndentation}"/>
                            <Rectangle Width="22" Margin="0,0,1,0"/>
                            <Rectangle Stroke="{TemplateBinding BorderBrush}" 
                                  StrokeThickness="0.5" Width="1"/>
                            <Grid>
                                <Rectangle Fill="{TemplateBinding Background}" />
                                <Rectangle 
                                  Fill="{StaticResource DefaultReflectVertical}" 
                                  Margin="1,1,1,0" />
                                <Rectangle 
                                  Stroke="{TemplateBinding BorderBrush}" 
                                  Margin="-1,0,0,0" />
                                <g:GContentPresenter
                                    x:Name="ELEMENT_ContentPresenter"
                                    Content="{TemplateBinding Content}"
                                    ContentTemplate="{TemplateBinding ContentTemplate}"
                                    Cursor="{TemplateBinding Cursor}"
                                    OrientatedHorizontalAlignment=
                                      "{TemplateBinding HorizontalContentAlignment}"
                                    OrientatedMargin="{TemplateBinding Padding}"
                                    OrientatedVerticalAlignment=
                                      "{TemplateBinding VerticalContentAlignment}" 
                                    PresenterOrientation=
                                      "{TemplateBinding PresenterOrientation}"/>
                            </Grid>
                        </g:GStackPanel>
                    </Grid>
                </g:GCanvas>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

此样式包含将显示在ContentPresenter下方的背景。它由两个矩形组成,这两个矩形分别用Background画笔和DefaultReflectVertical画笔填充。

样式还使用水平StackPanel,以便能够像缩进行一样缩进标题(参见Container_RowNodeStyle)。RootCanvas将用于根据网格的HorizontalOffset移动标题。

现在让我们更新HeadersContainer类,以利用这种新样式。

让我们首先增强HeadersContainer类以动态加载样式。

public HeadersContainer()
{
    this.DefaultStyleKey = typeof(HeadersContainer);
    this.LayoutUpdated += new EventHandler(HeadersContainer_LayoutUpdated);
}

private void HeadersContainer_LayoutUpdated(object sender, EventArgs e)
{
    if (!styleApplied)
    {
        this.ApplyStyle();
    }
}


private bool styleApplied;
private void ApplyStyle()
{
    Style style = ResourceHelper.FindResource("HeadersContainerStyle") as Style;
    if (style != null)
    {
        if (this.Style == null)
        {
            this.Style = style;
            styleApplied = true;                    
        }
    }
}

接下来要做的是向类添加FullIndentationIndentation属性。FullIdentation属性值将从IndentationSourceLevel属性值计算得出。它在样式中使用,以确保HeadersContainer的内容像行内容一样(即HandyContainer的项)被缩进。

public class HeadersContainer : GContentControl
{
    public static readonly DependencyProperty FullIndentationProperty;
    public static readonly DependencyProperty IndentationProperty;
    public static readonly DependencyProperty SourceLevelProperty;

    private bool isInReadOnlyChange;

    static HeadersContainer()
    {
        FullIndentationProperty = DependencyProperty.Register("FullIndentation", 
            typeof(double), typeof(HeadersContainer), 
            new PropertyMetadata(new PropertyChangedCallback(OnFullIndentationChanged)));
        IndentationProperty = DependencyProperty.Register("Indentation", 
            typeof(double), typeof(HeadersContainer), 
            new PropertyMetadata(new PropertyChangedCallback(OnIndentationChanged)));
        SourceLevelProperty = DependencyProperty.Register("SourceLevel", 
            typeof(int), typeof(HeadersContainer), 
            new PropertyMetadata(1, new PropertyChangedCallback(OnSourceLevelChanged)));
    }

    public int SourceLevel
    {
        get { return (int)GetValue(SourceLevelProperty); }
        set { SetValue(SourceLevelProperty, value); }
    }

    private static void OnSourceLevelChanged(DependencyObject d, 
                   DependencyPropertyChangedEventArgs e)
    {
        (d as HeadersContainer)._OnSourceLevelChanged((int)e.NewValue);
    }

    private void _OnSourceLevelChanged(int newValue)
    {
        UpdateIndentation();

    }

    public double Indentation
    {
        get { return (double)GetValue(IndentationProperty); }
        set { SetValue(IndentationProperty, value); }
    }

    private static void OnIndentationChanged(DependencyObject d, 
            DependencyPropertyChangedEventArgs e)
    {
        (d as HeadersContainer)._OnIndentationChanged((double)e.NewValue);
    }

    private void _OnIndentationChanged(double newValue)
    {
        UpdateIndentation();

    }

    public double FullIndentation
    {
        get { return (double)this.GetValue(FullIndentationProperty); }
        private set { this.SetValue(FullIndentationProperty, value); }
    }

    private static void OnFullIndentationChanged(DependencyObject d, 
                   DependencyPropertyChangedEventArgs e)
    {
        HeadersContainer item = (HeadersContainer)d;
        if (!item.isInReadOnlyChange)
            throw new InvalidOperationException(
               "FullIndentation property is read only");
    }

    protected virtual void UpdateIndentation()
    {
        isInReadOnlyChange = true;
        FullIndentation = SourceLevel * Indentation;
        isInReadOnlyChange = false;
    }

我们还需要修改HandyContainerGetHeadersContainer方法,以便用正确的值填充HeadersContainerIndentation属性:

internal HeadersContainer GetHeadersContainer(Type dataType, int level, ContainerItem item)
{
    ...

    HeadersContainer result = null;
    if (resultCache != null)
    {
        headersContainerCacheCollection.Remove(resultCache);
        result = resultCache.HeadersContainer;
    }
    else
    {
        result = new HeadersContainer();
        result.SourceLevel = level;
        result.Indentation = item.Indentation;

        if (headersCanvas != null)
            headersCanvas.Children.Add(result);
    }

    return result;
}

接下来要做的是使用RootCanvas根据网格的HorizontalOffset移动标题容器。

让我们修改HeadersContainer类的OnApplyTemplate方法。

private GCanvas rootCanvas;
private Grid layoutRoot;
public override void OnApplyTemplate()
{
    base.OnApplyTemplate();                   

    if (rootCanvas != null)
        rootCanvas.SizeChanged -= new SizeChangedEventHandler(rootCanvas_SizeChanged);

    rootCanvas = this.GetTemplateChild("RootCanvas") as GCanvas;
    if (rootCanvas != null)
    {
        rootCanvas.SizeChanged += new SizeChangedEventHandler(rootCanvas_SizeChanged);
    }

    if (layoutRoot != null)
        layoutRoot.SizeChanged -= new SizeChangedEventHandler(layoutRoot_SizeChanged);

    layoutRoot = this.GetTemplateChild("LayoutRoot") as Grid;
    if (layoutRoot != null)
    {
        if (rootCanvas != null)
        {
            rootCanvas.Height = layoutRoot.ActualHeight;
            UpdateHorizontalSettings();
        }

        layoutRoot.SizeChanged += new SizeChangedEventHandler(layoutRoot_SizeChanged);
    }
}

void rootCanvas_SizeChanged(object sender, SizeChangedEventArgs e)
{
    UpdateHorizontalSettings();
}

void layoutRoot_SizeChanged(object sender, SizeChangedEventArgs e)
{
    if (rootCanvas != null)
        rootCanvas.Height = layoutRoot.ActualHeight;

}

private void UpdateHorizontalSettings()
{
    if ((layoutRoot != null) && (rootCanvas != null) && (gridBody != null))
    {
        layoutRoot.Width = rootCanvas.ActualWidth + gridBody.HorizontalOffset;
        rootCanvas.HorizontalOffset = gridBody.HorizontalOffset;

    }
}

在下面的某些方法中,我们引用了一个gridBody字段。的确,为了能够根据网格的HorizontalOffset修改标题的位置,我们需要一个对网格的引用。

因此,让我们向HeadersContainer类添加一个GridBody属性:

private HandyContainer gridBody;
internal HandyContainer _GridBody
{
    get { return gridBody; }
    set
    {
        if (gridBody != value)
        {
            if (gridBody != null)
                RemoveGridBody();
            gridBody = value;
            if (isLoaded && isTemplateApplied)
                PrepareGridBody();
        }
    }
}

private void RemoveGridBody()
{
    if (prepared)
    {
        gridBody.HorizontalOffsetChanged -= 
              new EventHandler(gridBody_HorizontalOffsetChanged);
        prepared = false;
    }
}

bool prepared = false;
private void PrepareGridBody()
{
    if (rootCanvas != null)
    {
        UpdateHorizontalSettings();
    }

    if (!prepared)
    {
        gridBody.HorizontalOffsetChanged += 
               new EventHandler(gridBody_HorizontalOffsetChanged);
        prepared = true;
    }

}

private void gridBody_HorizontalOffsetChanged(object sender, EventArgs e)
{
    UpdateHorizontalSettings();
}

PrepareGridBody(调用UpdateHorizontalSettings)必须在HeadersContainer完全加载并应用模板后调用。因为,在使用Silverlight时,有时Loaded事件发生在OnApplyTemplate之前,有时发生在之后,我们必须使用isLoadedisTemplateApplied标志来跟踪对方法和事件的调用。

让我们向我们的类添加一个Loaded事件:

public HeadersContainer()
{
    this.DefaultStyleKey = typeof(HeadersContainer);
    this.LayoutUpdated += new EventHandler(HeadersContainer_LayoutUpdated);
    this.Loaded += new RoutedEventHandler(HeadersContainer_Loaded);
}

private bool isLoaded;
void HeadersContainer_Loaded(object sender, RoutedEventArgs e)
{
    isLoaded = true;
    if (isTemplateApplied)
    {
        if (gridBody != null)
            PrepareGridBody();

    }
}

让我们修改OnApplyTemplate类:

private bool isTemplateApplied;
private GCanvas rootCanvas;
private Grid layoutRoot;
public override void OnApplyTemplate()
{
    base.OnApplyTemplate();

    isTemplateApplied = true;

    if (rootCanvas != null)
        rootCanvas.SizeChanged -= 
          new SizeChangedEventHandler(rootCanvas_SizeChanged);

    rootCanvas = this.GetTemplateChild("RootCanvas") as GCanvas;
    if (rootCanvas != null)
    {
        rootCanvas.SizeChanged += 
          new SizeChangedEventHandler(rootCanvas_SizeChanged);
    }

    if (layoutRoot != null)
        layoutRoot.SizeChanged -= 
          new SizeChangedEventHandler(layoutRoot_SizeChanged);

    layoutRoot = this.GetTemplateChild("LayoutRoot") as Grid;
    if (layoutRoot != null)
    {
        if (rootCanvas != null)
        {
            rootCanvas.Height = layoutRoot.ActualHeight;
            UpdateHorizontalSettings();
        }

        layoutRoot.SizeChanged += 
          new SizeChangedEventHandler(layoutRoot_SizeChanged);
    }

    if (isLoaded)
    {
        if (gridBody != null)
            PrepareGridBody();
    }
}

我们仍然需要修改HandyContainerGetHeadersContainer方法,以便填充HeadersContainer_GridBody属性:

internal HeadersContainer GetHeadersContainer(Type dataType, int level, ContainerItem item)
{
    ...
    if (resultCache != null)
    {
        headersContainerCacheCollection.Remove(resultCache);
        result = resultCache.HeadersContainer;
    }
    else
    {
        result = new HeadersContainer();
        result.SourceLevel = level;
        result.Indentation = item.Indentation;
        result._GridBody = this;

        if (headersCanvas != null)
            headersCanvas.Children.Add(result);
    }

    return result;
}

如果我们现在启动应用程序,我们可以看到,如果我们水平滚动网格,HeadersContainer的位置会相应移动。HeadersContainer已正确缩进,并且在其内容下方显示了背景。

向HeadersContainer添加标题

现在我们有了显示在网格内正确位置的HeadersContainer,我们需要用标题填充它们。

创建Header类

让我们创建Header类,并且暂时将其保持非常简单。

GoaOpen\Extensions\Grid文件夹中,让我们添加Header类。

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Diagnostics;
using Netika.Windows.Controls;
using System.Collections.ObjectModel;

namespace Open.Windows.Controls
{
    public class Header : ContentControl
    {
        public Header()
        {
            DefaultStyleKey = typeof(Header);
        }        
    }
}

让我们还在generic.xaml文件末尾添加一个Header样式。

<Style TargetType="o:Header">
    <Setter Property="HorizontalContentAlignment" Value="Left" />
    <Setter Property="VerticalContentAlignment" Value="Center" />
    <Setter Property="FontSize" Value="11" />
    <Setter Property="FontWeight" Value="Normal" />
    <Setter Property="IsTabStop" Value="False" />
    <Setter Property="Padding" Value="5,4,4,3" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="o:Header">
                <Grid >
                    <vsm:VisualStateManager.VisualStateGroups>                            
                    </vsm:VisualStateManager.VisualStateGroups>                        
                    <ContentPresenter
                        Content="{TemplateBinding Content}"
                        Cursor="{TemplateBinding Cursor}"
                        HorizontalAlignment=
                          "{TemplateBinding HorizontalContentAlignment}"
                        VerticalAlignment=
                          "{TemplateBinding VerticalContentAlignment}"
                        Margin="{TemplateBinding Padding}"/>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

加载标题

标题的布局必须与它们链接的单元格完全相同。

为了实现此目标,我们将遵循以下步骤:

  • 将链接到的项之一的DataTemplate应用于HeadersContainerContentTemplate属性。
  • 将链接到的项之一的Content应用于HeadersContainerContent属性。

因此,HeadersContainer的显示内容将与它链接的项之一的显示内容完全相同。之后,我们将应用最后一步:

  • HeadersContainer显示内容中的单元格替换为标题。

结果将是位置和布局与链接项的单元格完全相同的标题。

让我们向HeadersContainer类添加一个SourceDataSample属性。此属性将允许定义必须链接到HeadersContainer的数据。

由于PrepareGridBody方法依赖于SourceDataSample属性的值,因此也必须在其值更改时调用它:

private object sourceDataSample;
public object SourceDataSample
{
    get { return sourceDataSample; }
    set
    {
        if (sourceDataSample != value)
        {
            sourceDataSample = value;
            OnSourceDataSampleChanged(EventArgs.Empty);
        }
    }
}

protected void OnSourceDataSampleChanged(EventArgs e)
{
    if (isLoaded && isTemplateApplied)
        if (gridBody != null)
            PrepareGridBody();
}

此属性将用链接到HeadersContainer的项之一的内容填充。

让我们还修改HeadersContainerPrepareGridBody方法,以初始化HeadersContainerContentTemplateContent

private void PrepareGridBody()
{
    this.ContentTemplate = gridBody.ItemTemplate;
    this.Content = SourceDataSample;
    this.DataContext = SourceDataSample;


    if (rootCanvas != null)
    {
        UpdateHorizontalSettings();
    }

    if (!prepared)
    {
        gridBody.HorizontalOffsetChanged += 
          new EventHandler(gridBody_HorizontalOffsetChanged);
        prepared = true;
    }
}

让我们修改HandyContainerGetHeadersContainer方法,以便使用项的ContentContentTemplate填充HeadersContainerContentContentTemplate

internal HeadersContainer GetHeadersContainer(Type dataType, int level, 
                                              ContainerItem item)
{
    ...
    HeadersContainer result = null;
    if (resultCache != null)
    {
        headersContainerCacheCollection.Remove(resultCache);
        result = resultCache.HeadersContainer;
    }
    else
    {
        result = new HeadersContainer();
        result.SourceLevel = level;
        result.Indentation = item.Indentation;
        result.Content = GetItemSource(item);
        result.DataContext = result.Content;
        result.ContentTemplate = this.ItemTemplate;
        result._GridBody = this;

        if (headersCanvas != null)
            headersCanvas.Children.Add(result);
    }

    return result;
}

我们还需要修改ContainerItem类的InitializeHeadersContainer方法,以便它删除在Content属性中临时进行的初始化。

private void InitializeHeadersContainer()
{
    HandyContainer handyContainer = HandyContainer.GetParentContainer(this);

    if (headersContainer != null)
    {
        if ((headersContainer.Content.GetType() == this.Content.GetType()) &&
            (headersContainer.SourceLevel == this.ParentCount))
            return;
        else
            RemoveHeadersContainer(handyContainer);
    }

    if (handyContainer != null)
    {
        headersContainer = handyContainer.GetHeadersContainer(
                              this.Content.GetType(), this.ParentCount, this);

        headersContainer.Width = this.ActualWidth;
        headers.Height = headersContainer.ActualHeight;
        headersContainer.SizeChanged += 
          new SizeChangedEventHandler(headersContainer_SizeChanged);
    }
}

如果我们现在启动应用程序,我们可以看到HeadersContainer填充了与它链接的项相同的单元格。

为了从HeadersContainer中删除这些单元格并用标题替换它们,让我们向HeadersContainer类添加一个RegsiterCell方法。此方法的作用是用标题替换单元格(传递给方法的参数)。它还将格式化标题,使其大小与单元格相同,并用单元格Header属性的值填充其内容。

但首先,让我们向HeadersContainer类添加一个静态的GetParentHeadersContainer方法。我们需要它来查找单元格的父HeadersContainer

public static HeadersContainer GetParentHeadersContainer(FrameworkElement element)
{
    DependencyObject parentElement = element;
    while (parentElement != null)
    {
        HeadersContainer parentContainer = parentElement as HeadersContainer;
        if (parentContainer != null)
            return parentContainer;

        parentElement = VisualTreeHelper.GetParent(parentElement);
    }

    return null;
}

现在让我们创建RegisterCell方法。

private List<Header> headersList;
internal void RegisterCell(Cell cell)
{
    if (headersList == null)
        headersList = new List<Header>();

    Header cellHeader = new Header();
    
    if (cell.Header == null)
        cellHeader.Content = cell.Name;
    else
        cellHeader.Content = cell.Header;

    HandyContainer parentContainer = gridBody;
    int parentCount = SourceLevel;

    if ((parentContainer == null) || (parentCount <= 0))
        return;

    cellHeader.Height = cell.ActualHeight;
    
    cellHeader.Width = cell.ActualWidth;
    
    cellHeader.MinHeight = cell.MinHeight;
    cellHeader.MaxHeight = cell.MaxHeight;
    cellHeader.MinWidth = cell.MinWidth;
    cellHeader.MaxWidth = cell.MaxWidth;

    cellHeader.VerticalAlignment = cell.VerticalAlignment;
    cellHeader.HorizontalAlignment = cell.HorizontalAlignment;

    GDock dock = (GDock)cell.GetValue(GDockPanel.DockProperty);
    if (dock != GDock.None)
        cellHeader.SetValue(GDockPanel.DockProperty, dock);

    headersList.Add(cellHeader);

    object cellParent = VisualTreeHelper.GetParent(cell);
    Panel parentPanel = cellParent as Panel;
    if (parentPanel != null)
    {
        int childCellIndex = parentPanel.Children.IndexOf(cell);
        parentPanel.Children[childCellIndex] = cellHeader;

    }
    else
    {
        Border parentBorder = cellParent as Border;
        if (parentBorder != null)
        {
            parentBorder.Child = cellHeader;
        }
        else
        {
            ContentControl parentContentControl = cellParent as ContentControl;
            if (parentContentControl != null)
            {
                parentContentControl.Content = cellHeader;
            }
            else
            {
                ContentPresenter parentContentPresenter = 
                                 cellParent as ContentPresenter;
                if (parentContentPresenter != null)
                {
                    parentContentPresenter.Content = cellHeader;
                }
            }
        }
    }
}

RegisterCell方法必须在正确的时间调用。如果调用太早,单元格尚未布局,标题的大小将无法正确初始化。如果调用太晚,用户在单元格被标题替换之前就有可能与之交互。

让我们向Cell类添加一个RegisterCell方法。该方法将调用父HeadersContainerRegisterCell方法(如果存在)。

private void RegisterCell()
{
    HeadersContainer headersContainer = 
            HeadersContainer.GetParentHeadersContainer(this);
    if (headersContainer != null)
        headersContainer.RegisterCell(this);
}

RegisterCell方法将在单元格完全加载后(已加载并应用了模板)被调用。由于在Silverlight中无法预测Loaded事件和OnApplyTemplate方法的调用顺序,我们将必须使用isLoadedisTemplateApplied标志来跟踪对方法和事件的调用。

让我们首先在Cell类中实现Loaded事件。

public Cell()
{
    this.BindingValidationError += 
      new EventHandler<ValidationErrorEventArgs>(Cell_BindingValidationError);
    this.Loaded += new RoutedEventHandler(Cell_Loaded);
}

private bool isLoaded;
void Cell_Loaded(object sender, RoutedEventArgs e)
{
    if (isTemplateApplied)
        RegisterCell();


    isLoaded = true;
}

然后让我们修改OnApplyTemplate方法。

private bool isTemplateApplied;
public override void OnApplyTemplate()
{
    base.OnApplyTemplate();

    if (string.IsNullOrEmpty(this.Name))
        throw new InvalidCastException("A cell must have a name");

    if (isLoaded)
        RegisterCell();

    isTemplateApplied = true;
}

如果我们现在启动应用程序,我们可以看到标题显示在headersContainerss中的正确位置。

Header样式

我们需要增强标题的样式,以使标题更具吸引力。

同时,我们将在标题样式中添加一个WindowsSizer。它将允许用户调整标题的大小。标题中的WindowSizerVerticalSizeModeHorizontalSizeMode将绑定到标题的VerticalSizeModeHorizontalSizeMode属性。

让我们将这两个属性添加到Header类中:

public class Header : ContentControl
{
    public static readonly DependencyProperty VerticalSizeModeProperty;
    public static readonly DependencyProperty HorizontalSizeModeProperty;

    static Header()
    {
        VerticalSizeModeProperty = DependencyProperty.Register(
          "VerticalSizeMode", typeof(VerticalSizerMode), typeof(Header), null);
        HorizontalSizeModeProperty = DependencyProperty.Register(
          "HorizontalSizeMode", typeof(HorizontalSizerMode), typeof(Header), null);
    }

    public VerticalSizerMode VerticalSizeMode
    {
        get { return (VerticalSizerMode)GetValue(VerticalSizeModeProperty); }
        set { SetValue(VerticalSizeModeProperty, value); }
    }
    
    public HorizontalSizerMode HorizontalSizeMode
    {
        get { return (HorizontalSizerMode)GetValue(HorizontalSizeModeProperty); }
        set { SetValue(HorizontalSizeModeProperty, value); }
    }

generic.xaml文件末尾找到标题样式,并用此样式替换它:

<ControlTemplate x:Key="EmptyBorderTemplate" 
               TargetType="g:SizerBorder">
    <Rectangle Fill="Transparent"/>
</ControlTemplate>

<Style x:Key="HeaderWindowSizerStyle" TargetType="g:WindowSizer">
    <Setter Property="VerticalSizeMode" Value="Bottom" />
    <Setter Property="HorizontalSizeMode" Value="Right" />
    <Setter Property="IsEnabled" Value="True" />
    <Setter Property="Background" Value="Transparent"/>
    <Setter Property="BorderBrush" Value="Transparent"/>
    <Setter Property="BorderThickness" Value="0"/>
    <Setter Property="HorizontalContentAlignment" Value="Stretch" />
    <Setter Property="VerticalContentAlignment" Value="Stretch" />
    <Setter Property="Cursor" Value="Arrow" />
    <Setter Property="MinWidth" Value="20"/>
    <Setter Property="MinHeight" Value="20"/>
    <Setter Property="FontSize" Value="11" />
    <Setter Property="IsTabStop" Value="False" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="g:WindowSizer" >
                <Grid>
                    <vsm:VisualStateManager.VisualStateGroups>
                        <vsm:VisualStateGroup x:Name="CommonStates">
                            <vsm:VisualState x:Name="Normal"/>
                            <vsm:VisualState x:Name="Disabled">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames 
                                      Storyboard.TargetName="DisabledVisual" 
                                      Storyboard.TargetProperty="Visibility" 
                                      Duration="0">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Visible</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </vsm:VisualState>
                        </vsm:VisualStateGroup>
                    </vsm:VisualStateManager.VisualStateGroups>

                    <Grid x:Name="ELEMENT_GridContainer">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="Auto"/>
                            <ColumnDefinition Width="*"/>
                            <ColumnDefinition Width="Auto"/>
                        </Grid.ColumnDefinitions>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="*"/>
                            <RowDefinition Height="Auto"/>
                        </Grid.RowDefinitions>
                        <ContentPresenter
                              x:Name="ELEMENT_ContentPresenter"
                              Grid.ColumnSpan="3"
                              Grid.RowSpan="3"
                              Content="{TemplateBinding Content}"
                              ContentTemplate="{TemplateBinding ContentTemplate}"
                              Cursor="{TemplateBinding Cursor}"
                              HorizontalAlignment=
                                 "{TemplateBinding HorizontalContentAlignment}"
                              VerticalAlignment=
                                 "{TemplateBinding VerticalContentAlignment}"
                              Margin="{TemplateBinding Padding}"  />

                        <g:SizerBorder 
                          x:Name="ELEMENT_RightSizeBorder" 
                          Grid.Column="2"
                          Grid.Row="1"
                          Width="5"
                          Cursor="SizeWE"
                            Template="{StaticResource EmptyBorderTemplate}"
                            />

                        <g:SizerBorder 
                          x:Name="ELEMENT_LeftSizeBorder" 
                          Grid.Column="0"
                          Grid.Row="1"
                          Width="5"
                          Cursor="SizeWE"
                          Template="{StaticResource EmptyBorderTemplate}"
                            />

                        <g:SizerBorder 
                          x:Name="ELEMENT_BottomSizeBorder" 
                          Grid.Column="1"
                          Grid.Row="2"
                          Height="5"
                          Cursor="SizeNS"
                            Template="{StaticResource EmptyBorderTemplate}"
                            />

                        <g:SizerBorder 
                          x:Name="ELEMENT_TopSizeBorder" 
                          Grid.Column="1"
                          Grid.Row="0"
                          Height="5"
                          Cursor="SizeNS"
                            Template="{StaticResource EmptyBorderTemplate}"
                            />

                        <g:SizerBorder 
                          x:Name="ELEMENT_BottomRightSizeCorner"
                          Grid.Column="2"
                          Grid.Row="2"
                          Width="5"
                          Height="5"
                          Cursor="Hand"
                          Template="{StaticResource EmptyBorderTemplate}"  
                            />

                        <g:SizerBorder 
                          x:Name="ELEMENT_BottomLeftSizeCorner"
                          Grid.Column="0"
                          Grid.Row="2"
                          Width="5"
                          Height="5"
                          Cursor="Hand"
                          Template="{StaticResource EmptyBorderTemplate}"  
                            />

                        <g:SizerBorder 
                          x:Name="ELEMENT_TopRightSizeCorner"
                          Grid.Column="2"
                          Grid.Row="0"
                          Width="5"
                          Height="5"
                          Cursor="Hand"
                          Template="{StaticResource EmptyBorderTemplate}"  
                            />

                        <g:SizerBorder 
                          x:Name="ELEMENT_TopLeftSizeCorner"
                          Grid.Column="0"
                          Grid.Row="0"
                          Width="5"
                          Height="5"
                          Cursor="Hand"
                          Template="{StaticResource EmptyBorderTemplate}"  
                            />
                        <Rectangle x:Name="DisabledVisual" 
                          Grid.RowSpan="3" Grid.ColumnSpan="3" 
                          StrokeThickness="6" 
                          Stroke="{StaticResource DefaultDisabled}" 
                          Visibility="Collapsed" Opacity="0.6" />
                    </Grid>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

<Style TargetType="o:Header">
    <Setter Property="Foreground" Value="{StaticResource DefaultForeground}" />
    <Setter Property="Background" Value="{StaticResource DefaultBackground}"/>
    <Setter Property="HorizontalContentAlignment" Value="Center" />
    <Setter Property="VerticalContentAlignment" Value="Center" />
    <Setter Property="FontSize" Value="11" />
    <Setter Property="FontWeight" Value="Normal" />
    <Setter Property="IsTabStop" Value="False" />
    <Setter Property="Padding" Value="5,4,4,3" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="o:Header">
                <Grid >
                    <vsm:VisualStateManager.VisualStateGroups>
                        <vsm:VisualStateGroup x:Name="CommonStates">
                            <vsm:VisualState x:Name="Normal" />
                            <vsm:VisualState x:Name="MouseOver">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames 
                                      Storyboard.TargetName="MouseOverVisual" 
                                      Storyboard.TargetProperty="Visibility" 
                                      Duration="0">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Visible</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </vsm:VisualState>
                        </vsm:VisualStateGroup>
                    </vsm:VisualStateManager.VisualStateGroups>
                    <g:WindowSizer 
                        x:Name="SizerElement" 
                            VerticalSizeMode="{TemplateBinding VerticalSizeMode}" 
                            HorizontalSizeMode="{TemplateBinding HorizontalSizeMode}"
                            MinWidth="{TemplateBinding MinWidth}"
                            MaxWidth="{TemplateBinding MaxWidth}"
                            MinHeight="{TemplateBinding MinHeight}"
                            MaxHeight="{TemplateBinding MaxHeight}"
                            Style="{StaticResource HeaderWindowSizerStyle}">
                        <Grid Background="Transparent" >
                            <Grid>
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="*" />
                                    <RowDefinition Height="*" />
                                    <RowDefinition Height="Auto" />
                                </Grid.RowDefinitions>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="Auto" />
                                    <ColumnDefinition Width="*" />
                                    <ColumnDefinition Width="Auto" />
                                </Grid.ColumnDefinitions>
                                <Grid Grid.ColumnSpan="3" Grid.RowSpan="3">
                                    <Grid.RowDefinitions>
                                        <RowDefinition Height="1*" />
                                        <RowDefinition Height="1*" />
                                    </Grid.RowDefinitions>
                                    <Rectangle x:Name="MouseOverVisual" 
                                      Fill="{StaticResource 
                                             DefaultDarkGradientBottomVertical}" 
                                      Grid.Row="1" Margin="1,1,2,2" 
                                      Visibility="Collapsed"/>
                                </Grid>
                                <ContentPresenter
                                    Grid.RowSpan="3"
                                    Content="{TemplateBinding Content}"
                                    Cursor="{TemplateBinding Cursor}"
                                    HorizontalAlignment=
                                      "{TemplateBinding HorizontalContentAlignment}"
                                    VerticalAlignment=
                                      "{TemplateBinding VerticalContentAlignment}"
                                    Margin="{TemplateBinding Padding}"/>
                            </Grid>
                            <Rectangle Width="1" 
                              Fill="{StaticResource DefaultStroke}" 
                              HorizontalAlignment="Right"/>
                        </Grid>
                    </g:WindowSizer>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

如果我们现在启动应用程序,我们可以看到标题几乎已正确渲染。

Header鼠标悬停

如果我们查看标题样式,我们可以看到CommonStates VisualStateGroup中有一个MouseOver VisualState,它允许使名为“MouseOverVisual”的元素可见或折叠。

为了使此VisualState正常工作,我们需要将必要的代码添加到Header类中:

public override void OnApplyTemplate()
{
    base.OnApplyTemplate();

    VisualStateManager.GoToState(this, "Normal", false);
}

protected override void OnMouseEnter(MouseEventArgs e)
{
    base.OnMouseEnter(e);
    if (this.IsEnabled)
        VisualStateManager.GoToState(this, "MouseOver", true);
}

protected override void OnMouseLeave(MouseEventArgs e)
{
    base.OnMouseLeave(e);
    VisualStateManager.GoToState(this, "Normal", true);
}

如果我们现在启动应用程序,我们可以看到当我们将鼠标指针移到它上面时,标题的背景会发生变化。

从单元格名称获取Header

为了能够从链接到它的单元格的名称访问标题,我们将向Header类添加一个CellName属性。

internal string CellName
{
    get;
    set;
}

当调用HeadersContainerRegisterCell方法时,此属性将被填充一个值。

private List<Header> headersList;
internal void RegisterCell(Cell cell)
{
    if (headersList == null)
        headersList = new List<Header>();

    Header cellHeader = new Header();

    cellHeader.CellName = cell.Name;

    ...

我们还将向HeadersContainer类添加一个FindHeader方法:

internal Header FindHeader(string cellName)
{
    if (headersList != null)
    {
        foreach (Header cellHeader in headersList)
            if (cellHeader.CellName == cellName)
                return cellHeader;
    }

    return null;
}

CellName属性和FindHeader方法将在下一步用于使标题的大小与它们链接的单元格的大小保持同步。

更广泛地说,此属性和方法可用于检索链接到单元格的标题或反之亦然。

调整标题大小

标题的样式中包含一个WindowSizer,但我们还没有“激活”它。让我们修改Header类,以考虑WindowSizer。单元格具有UserResizeType属性,允许定义它们是否可以调整大小以及如何调整大小(垂直、水平或两者)。让我们向Header类添加相同的属性,以了解它是否可以调整大小。

internal HeaderResizeTypes UserResizeType
{
    get;
    set;
}

由于我们在标题中使用WindowSizer,如果我们想从外部设置标题的大小(例如,进行初始化),我们不能通过设置其宽度和/或高度值来设置标题的大小。如果我们这样做,标题的大小将是固定的,当WindowSizer被调整大小时(用户拖动其边框之一),标题将不会自动调整大小。从外部设置标题大小时,我们需要设置WindowSizer的大小,而不是标题的大小。

让我们修改OnApplyTemplate方法,并向Header类添加SizerHeightSizerWidth方法。

private WindowSizer windowSizerElement;
public override void OnApplyTemplate()
{
    base.OnApplyTemplate();

    windowSizerElement = this.GetTemplateChild("SizerElement") as WindowSizer;
    if (windowSizerElement != null)
    {
        if ((UserResizeType & HeaderResizeTypes.Bottom) == HeaderResizeTypes.Right)
            windowSizerElement.Height = sizerHeight;

        if ((UserResizeType & HeaderResizeTypes.Right) == HeaderResizeTypes.Right)
            windowSizerElement.Width = sizerWidth;
    }

    VisualStateManager.GoToState(this, "Normal", false);
}

private double sizerHeight = double.NaN;
internal double SizerHeight
{
    get
    {
        if (windowSizerElement != null)
            return windowSizerElement.Height;

        return sizerHeight;
    }

    set
    {
        if (windowSizerElement != null)
            windowSizerElement.Height = value;
        else
            sizerHeight = value;
    }
}

private double sizerWidth = double.NaN;
internal double SizerWidth
{
    get
    {
        if (windowSizerElement != null)
            return windowSizerElement.Width;

        return sizerWidth;
    }

    set
    {
        if (windowSizerElement != null)
            windowSizerElement.Width = value;
        else
            sizerWidth = value;
    }
}

标题的VerticalSizeModeHorizontalSizeMode属性可以从UserResizeType属性计算得出。

因此,让我们将VerticalSizeModeHorizontalSizeMode属性设为只读,并根据UserResizeType值更新它们。

private bool isInReadOnlyChange;
static Header()
{
    VerticalSizeModeProperty = DependencyProperty.Register("VerticalSizeMode", 
      typeof(VerticalSizerMode), typeof(Header), 
      new PropertyMetadata(new PropertyChangedCallback(OnVerticalSizeModeChanged)));
    HorizontalSizeModeProperty = DependencyProperty.Register("HorizontalSizeMode", 
      typeof(HorizontalSizerMode), typeof(Header), 
      new PropertyMetadata(new PropertyChangedCallback(OnHorizontalSizeModeChanged)));
}

public Header()
{
    DefaultStyleKey = typeof(Header);
}

public VerticalSizerMode VerticalSizeMode
{
    get { return (VerticalSizerMode)GetValue(VerticalSizeModeProperty); }
    private set { SetValue(VerticalSizeModeProperty, value); }
}

private static void OnVerticalSizeModeChanged(DependencyObject d, 
        DependencyPropertyChangedEventArgs e)
{
    Header header = (Header)d;
    if (!header.isInReadOnlyChange)
        throw new InvalidOperationException(
           "VerticalSizeMode property is read only");
}

public HorizontalSizerMode HorizontalSizeMode
{
    get { return (HorizontalSizerMode)GetValue(HorizontalSizeModeProperty); }
    private set { SetValue(HorizontalSizeModeProperty, value); }
}

private static void OnHorizontalSizeModeChanged(DependencyObject d, 
        DependencyPropertyChangedEventArgs e)
{
    Header header = (Header)d;
    if (!header.isInReadOnlyChange)
        throw new InvalidOperationException(
             "nHorizontalSizeMode property is read only");
}

private WindowSizer windowSizerElement;
public override void OnApplyTemplate()
{
    base.OnApplyTemplate();

    windowSizerElement = this.GetTemplateChild("SizerElement") as WindowSizer;
    UpdateWindowSizerElementSizeMode();
    UpdateWindowSizerElementSize();

    VisualStateManager.GoToState(this, "Normal", false);
}

private void UpdateWindowSizerElementSizeMode()
{
    if (windowSizerElement != null)
    {
        isInReadOnlyChange = true;
        if ((UserResizeType & HeaderResizeTypes.Bottom) == HeaderResizeTypes.Bottom)
            windowSizerElement.VerticalSizeMode = VerticalSizerMode.Bottom;
        else
            windowSizerElement.VerticalSizeMode = VerticalSizerMode.None;

        if ((UserResizeType & HeaderResizeTypes.Right) == HeaderResizeTypes.Right)
            windowSizerElement.HorizontalSizeMode = HorizontalSizerMode.Right;
        else
            windowSizerElement.HorizontalSizeMode = HorizontalSizerMode.None;
        isInReadOnlyChange = false;
    }
}

private void UpdateWindowSizerElementSize()
{
    if (windowSizerElement != null)
    {
        windowSizerElement.Height = sizerHeight;
        windowSizerElement.Width = sizerWidth;

    }
}

让我们修改HeadersContainerRegisterCell方法,以初始化标题的UserResizeTypeSizerHeightSizerWidth属性:

internal void RegisterCell(Cell cell)
{
    if (headersList == null)
        headersList = new List<Header>();

    Header cellHeader = new Header();

    cellHeader.CellName = cell.Name;
    
    if (cell.Header == null)
        cellHeader.Content = cell.Name;
    else
        cellHeader.Content = cell.Header;

    HandyContainer parentContainer = gridBody;
    int parentCount = SourceLevel;

    if ((parentContainer == null) || (parentCount <= 0))
        return;

    cellHeader.UserResizeType = cell.UserResizeType;

    double childCellActualHeight = cell.ActualHeight;
    if (childCellActualHeight > 0)
        cellHeader.SizerHeight = childCellActualHeight;

    double childCellActualWidth = cell.ActualWidth;
    if (childCellActualWidth > 0)
        cellHeader.SizerWidth = cell.ActualWidth;
    
    cellHeader.MinHeight = cell.MinHeight;
    cellHeader.MaxHeight = cell.MaxHeight;
    cellHeader.MinWidth = cell.MinWidth;
    cellHeader.MaxWidth = cell.MaxWidth;

    ...
}

如果我们现在启动应用程序,我们可以看到我们可以调整标题的大小(默认情况下是右侧)。然而,当调整标题大小时,单元格的大小不会被修改。

保持单元格大小与标题大小同步

为了在调整标题大小时调整单元格大小,我们需要在标题调整大小时得到通知。

因此,让我们“连接”到标题中的WindowSizerUserResizingUserResizeComplete事件,并在Header类中添加UserResizingUserResizeComplete事件。

public override void OnApplyTemplate()
{
    base.OnApplyTemplate();

    windowSizerElement = 
      this.GetTemplateChild("SizerElement") as WindowSizer;
    if (windowSizerElement != null)
    {
        windowSizerElement.UserResizing += 
          new EventHandler(windowSizerElement_UserResizing);
        windowSizerElement.UserResizeComplete += 
          new EventHandler(windowSizerElement_UserResizeComplete);
    }

    UpdateWindowSizerElementSizeMode();
    UpdateWindowSizerElementSize();

    VisualStateManager.GoToState(this, "Normal", false);
}

public event EventHandler UserResizeComplete;
private void windowSizerElement_UserResizeComplete(object sender, EventArgs e)
{
    if (UserResizeComplete != null)
        UserResizeComplete(this, e);
}

public event EventHandler UserResizing;
private void windowSizerElement_UserResizing(object sender, EventArgs e)
{
    if (UserResizing != null)
        UserResizing(this, e);
}

让我们在HeaderContainerRegisterCell方法中“连接”到标题的UserResizingUserResizeComplete事件。

因为,当调整一个标题大小时,所有“链接”到该标题的单元格都必须调整大小,这个操作应该在HandyContainergridBody)内部进行。这是可以访问所有单元格的地方。因此,当调整标题大小时,我们将调用HandyContainer_HeaderSizeChanged方法(该方法将在下一步创建)。

internal void RegisterCell(Cell cell)
{
    ...
    
    cellHeader.MinHeight = cell.MinHeight;
    cellHeader.MaxHeight = cell.MaxHeight;
    cellHeader.MinWidth = cell.MinWidth;
    cellHeader.MaxWidth = cell.MaxWidth;

    cellHeader.UserResizeComplete += 
      new EventHandler(cellHeader_UserResizeComplete);
    cellHeader.UserResizing += new EventHandler(cellHeader_UserResizing);

    cellHeader.VerticalAlignment = cell.VerticalAlignment;
    cellHeader.HorizontalAlignment = cell.HorizontalAlignment;

    ...
}

void cellHeader_UserResizing(object sender, EventArgs e)
{
    HeaderResized(sender as Header);
}

void cellHeader_UserResizeComplete(object sender, EventArgs e)
{
    HeaderResized(sender as Header);
}

private void HeaderResized(Header cellHeader)
{
    if (gridBody != null)
        gridBody._HeaderSizeChanged(cellHeader.CellName, 
            this.DataContext.GetType(), 
            new Size(cellHeader.SizerWidth, cellHeader.SizerHeight), SourceLevel);
}

我们现在需要实现HandyContainer中的_HeaderSizeChanged方法。

当调整标题大小时,所有单元格:

  • 其名称与标题的CellName属性值匹配
  • 并且由位于与HeadersContainerSourceLevel对应的层次结构级别的项持有
  • 并且链接到与HeadersContainer的源元素类型相同的源元素

必须调整大小。

让我们创建_HeaderSizeChanged方法并实现这些规则:

internal void _HeaderSizeChanged(string cellName, Type itemType, Size size, int parentCount)
{
    if (this.ItemsHost == null)
        return;

    UIElementCollection itemsHostChildren = this.ItemsHost.Children;
    foreach (ContainerItem item in itemsHostChildren)
    {
        if ((item.DataContext.GetType() == itemType) && (item._ParentCount == parentCount))
        {
            Cell cell = item.FindCell(cellName);
            if (cell != null)
            {
                cell.Width = size.Width;
                cell.Height = size.Height;
            }
        }
    }
}

ContainerItem类的ParentCount属性允许知道该项有多少个父节点。因此,在上面的方法中,它允许我们知道项是否处于正确的层次结构级别。然而,Item类(ContainerItem继承自该类)的ParentCount属性是受保护的。为了能够访问其值,我们需要向ContainerItem类添加一个内部的_ParentCount属性。

internal int _ParentCount
{
    get { return this.ParentCount; }
}

如果我们现在启动应用程序,我们可以看到当标题调整大小时,单元格也会调整大小。然而,仍然存在两个问题:

  • 当调整一个标题大小时,链接到相同单元格的其他标题不会调整大小。
  • 如果我们调整网格滚动条大小后滚动网格,一段时间后,会显示大小不正确的单元格的项。

第一个问题源于没有任何东西警告标题其链接的单元格已被调整大小。第二个问题源于当创建新项时,其单元格获得默认大小值,而不是用户定义的新大小。

为了能够解决这些问题,我们需要能够保存和检索已调整大小的单元格的大小。

让我们向HandyContainer类添加SetCellWidthSetCellHeightGetCellWidthGetCellHeight方法。

private Dictionary<string, double> cellWidths;
internal double GetCellWidth(string cellName, int parentCount)
{
    if (cellWidths == null)
        return -1;

    string key = cellName + 
     parentCount.ToString(CultureInfo.InvariantCulture.NumberFormat);

    if (!cellWidths.ContainsKey(key))
        return -1;

    return cellWidths[key];
}

internal void SetCellWidth(string cellName, int parentCount, double cellWidth)
{
    if (cellWidths == null)
        cellWidths = new Dictionary<string, double>();

    string key = cellName + 
      parentCount.ToString(CultureInfo.InvariantCulture.NumberFormat);

    cellWidths[key] = cellWidth;
}

private Dictionary<string, double> cellHeights;
internal double GetCellHeight(string cellName, int parentCount)
{
    if (cellHeights == null)
        return -1;

    string key = cellName + 
      parentCount.ToString(CultureInfo.InvariantCulture.NumberFormat);

    if (!cellHeights.ContainsKey(key))
        return -1;

    return cellHeights[key];
}

internal void SetCellHeight(string cellName, int parentCount, double cellHeight)
{
    if (cellHeights == null)
        cellHeights = new Dictionary<string, double>();

    string key = cellName + 
      parentCount.ToString(CultureInfo.InvariantCulture.NumberFormat);

    cellHeights[key] = cellHeight;
}

我们需要在标题调整大小时尽快调用SetCellWidthSetCellHeight方法(在HandyContainer类的_HeaderSizeChanged方法中)。我们还需要在此方法中添加一个事件。

让我们修改_HeaderSizeChange方法以考虑这些新规则:

public event EventHandler<CellSizeChangedEventArgs> CellSizeChanged;
internal void _HeaderSizeChanged(string cellName, 
         Type itemType, Size size, int parentCount)
{
    if (this.ItemsHost == null)
        return;

    UIElementCollection itemsHostChildren = this.ItemsHost.Children;
    foreach (ContainerItem item in itemsHostChildren)
    {
        if ((item.DataContext.GetType() == itemType) && 
            (item._ParentCount == parentCount))
        {
            Cell cell = item.FindCell(cellName);
            if (cell != null)
            {
                cell.Width = size.Width;
                cell.Height = size.Height;
            }
        }
    }

    this.SetCellWidth(cellName, parentCount, size.Width);
    this.SetCellHeight(cellName, parentCount, size.Height);

    if (CellSizeChanged != null)
        CellSizeChanged(this, new CellSizeChangedEventArgs(
                              cellName, itemType, size, parentCount));
}

让我们还创建在_HeaderSizeChange方法中使用的CellSizeChangedEventArgs

using System;
using System.Windows;

namespace Open.Windows.Controls
{
    public class CellSizeChangedEventArgs : EventArgs
    {
        public CellSizeChangedEventArgs(string cellName, 
               Type itemType, Size size, int level)
        {
            this.CellName = cellName;
            this.ItemType = itemType;
            this.Size = size;
            this.Level = level;
        }


        public string CellName
        {
            get;
            private set;
        }

        public Type ItemType
        {
            get;
            private set;
        }

        public Size Size
        {
            get;
            private set;
        }

        public int Level
        {
            get;
            private set;
        }
    }
}

接下来,让我们修改HeadersContainer类,并连接到CellSizeChanged事件以相应地更新标题大小:

private void PrepareGridBody()
{
    if (rootCanvas != null)
    {
        UpdateHorizontalSettings();
    }

    if (!prepared)
    {
        gridBody.HorizontalOffsetChanged += 
          new EventHandler(gridBody_HorizontalOffsetChanged);
        gridBody.CellSizeChanged += new 
          EventHandler<CellSizeChangedEventArgs>(gridBody_CellSizeChanged);
        prepared = true;
    }

}

private void RemoveGridBody()
{
    if (prepared)
    {
        gridBody.HorizontalOffsetChanged -= 
          new EventHandler(gridBody_HorizontalOffsetChanged);
        gridBody.CellSizeChanged -= new 
          EventHandler<CellSizeChangedEventArgs>(gridBody_CellSizeChanged);
        prepared = false;
    }
}

private void gridBody_CellSizeChanged(object sender, CellSizeChangedEventArgs e)
{
    if ((SourceLevel == e.Level))
    {
        Header cellHeader = FindHeader(e.CellName);
        if (cellHeader != null)
        {
            cellHeader.SizerWidth = e.Size.Width;
            cellHeader.SizerHeight = e.Size.Height;
        }
    }
}

让我们还更新HeadersContainer类的RegisterCell方法,以考虑HandyContainerGetCellWidthGetCellHeight方法提供的值:

internal void RegisterCell(Cell cell)
{
    if (headersList == null)
        headersList = new List<Header>();

    Header cellHeader = new Header();

    cellHeader.CellName = cell.Name;
    
    if (cell.Header == null)
        cellHeader.Content = cell.Name;
    else
        cellHeader.Content = cell.Header;

    HandyContainer parentContainer = gridBody;
    int parentCount = SourceLevel;

    if ((parentContainer == null) || (parentCount <= 0))
        return;

    cellHeader.UserResizeType = cell.UserResizeType;

    double height = parentContainer.GetCellHeight(cell.Name, parentCount);
    if (height >= 0)
        cellHeader.SizerHeight = height;
    else
    {
        double childCellActualHeight = cell.ActualHeight;
        if (childCellActualHeight > 0)
            cellHeader.SizerHeight = childCellActualHeight;
    }

    double width = parentContainer.GetCellWidth(cell.Name, parentCount);
    if (width >= 0)
        cellHeader.SizerWidth = width;
    else
    {
        double childCellActualWidth = cell.ActualWidth;
        if (childCellActualWidth > 0)
            cellHeader.SizerWidth = cell.ActualWidth;
    }
    
    cellHeader.MinHeight = cell.MinHeight;
    cellHeader.MaxHeight = cell.MaxHeight;
    cellHeader.MinWidth = cell.MinWidth;
    cellHeader.MaxWidth = cell.MaxWidth;

    ...
}

最后,让我们根据HandyContainerGetCellWidthGetCellHeight方法返回的值初始化单元格的大小:

让我们向Cell类添加一个InitializeSize方法:

private void InitializeSize()
{
    HandyContainer parentContainer = HandyContainer.GetParentContainer(this);
    if (parentContainer != null)
    {
        ContainerItem parentItem = ContainerItem.GetParentContainerItem(this);

        double width = parentContainer.GetCellWidth(this.Name, parentItem._ParentCount);
        if (width >= 0)
            this.Width = width;

        double height = parentContainer.GetCellHeight(this.Name, parentItem._ParentCount);
        if (height >= 0)
            this.Height = height;
    }
}

让我们从单元格的OnApplyTemplate方法和单元格的Loaded事件中调用InitialSize方法:

void Cell_Loaded(object sender, RoutedEventArgs e)
{
    InitializeSize();

    if (isTemplateApplied)
        RegisterCell();

    isLoaded = true;
}

public override void OnApplyTemplate()
{
    base.OnApplyTemplate();

    if (string.IsNullOrEmpty(this.Name))
        throw new InvalidCastException("A cell must have a name");

    InitializeSize();

    if (isLoaded)
        RegisterCell();

    isTemplateApplied = true;
}

如果我们现在尝试启动应用程序,在调用单元格的InitializeSize方法时会失败。如果我们深入研究,我们可以看到ContainerItemGetParentContainerItem方法返回了一个意外的null值。

为什么一个单元格没有父ContainerItem

事实上,单元格是由HeadersContainer在填充其ContentTemplateContent属性时生成的单元格之一。由于单元格是HeadersContainer的一部分,因此它没有父ContainerItem

因此,让我们向InitialSize方法添加一个条件:

private void InitializeSize()
{
    HandyContainer parentContainer = HandyContainer.GetParentContainer(this);
    if (parentContainer != null)
    {
        ContainerItem parentItem = ContainerItem.GetParentContainerItem(this);

        if (parentItem != null)
        {
            double width = parentContainer.GetCellWidth(this.Name, 
                                           parentItem._ParentCount);
            if (width >= 0)
                this.Width = width;

            double height = parentContainer.GetCellHeight(this.Name, 
                                            parentItem._ParentCount);
            if (height >= 0)
                this.Height = height;
        }
    }
}

如果我们启动应用程序并调整标题大小,我们可以看到单元格和标题的大小现在保持同步。

注意

当使用SetCellWidthSetCellHeight方法保存了单元格宽度或高度时,该值会一直保存在内存中,直到相应的HandyContainer被销毁。这意味着,如果修改了ContainerItemItemsSource的值以在网格中显示不同类型的数据,则脏单元格的宽度和高度值会保留在内存中。在网格的增强版本中,当修改ItemsSource值时,我们应该清除cellWidthscellHeights字典的内容。

5. 顶部标题

引言

我们的网格现在能够显示网格主体内的标题。我们仍然需要实现顶部标题。让我们看看如果我们把我们创建的HeadersContainer放在网格顶部会发生什么。为了测试目的,我们将为国家行、地区行和人员行添加一个顶部标题。

让我们修改GridBody项目的Page.xaml文件,并在GridBody顶部添加标题。

<Grid x:Name="LayoutRoot" Background="White">
    <g:GDockPanel>
        <Button Content="Focus Button" 
           g:GDockPanel.Dock="Top" Margin="5" Width="200"/>
        <o:HeadersContainer x:Name="CountryHeaders" g:GDockPanel.Dock="Top"/>
        <o:HeadersContainer x:Name="RegionHeaders" g:GDockPanel.Dock="Top"/>
        <o:HeadersContainer x:Name="PersonHeaders" g:GDockPanel.Dock="Top"/>
        <o:HandyContainer
           x:Name="MyGridBody"
           VirtualMode="On"
           AlternateType="Items"
           ...

为了能够将顶部HeadersContainer链接到我们的GridBodyHeaderContainer必须有一个允许这样做的公共属性。

让我们向HeadersContainer类添加一个GridBody属性:

public HandyContainer GridBody
{
    get { return _GridBody; }
    set { _GridBody = value; }
}

让我们在Page.xaml.cs文件中的构造函数中执行我们顶部HeadersContainer和我们GridBody之间的“链接”。

public Page()
{
    InitializeComponent();

    CreateData();
    MyGridBody.ItemsSource = countryCollection;

    CountryHeaders.GridBody = MyGridBody;
    CountryHeaders.SourceLevel = 1;
    CountryHeaders.Content = new Country(" ", " ");
    CountryHeaders.DataContext = CountryHeaders.Content;
    CountryHeaders.ContentTemplate = MyGridBody.ItemTemplate;

    RegionHeaders.GridBody = MyGridBody;
    RegionHeaders.SourceLevel = 2;
    RegionHeaders.Content = new StateProvince(" ", " ", " ");
    RegionHeaders.DataContext = RegionHeaders.Content;
    RegionHeaders.ContentTemplate = MyGridBody.ItemTemplate;

    PersonHeaders.GridBody = MyGridBody;
    PersonHeaders.SourceLevel = 3;
    PersonHeaders.Content = new Person(" ", " ", " ", " ", " ", " ", " ", " ", 0, true);
    PersonHeaders.DataContext = PersonHeaders.Content;
    PersonHeaders.ContentTemplate = MyGridBody.ItemTemplate;

}

注意,在上面的代码中,我们用空格值填充了CountryStateProvincePerson类实例的几乎所有字段值。由于这些实例的数据未显示(单元格被替换为标题),我们可以使用任何值来填充这些实例的字段。然而,最好避免使用null或空值。标题的大小是根据它们替换的单元格的大小计算的。包含空值或null值的单元格可能具有意外的大小。

让我们启动我们的应用程序并观察我们的更改。

标题显示在顶部,并且它们按预期工作。然而,我们不得不在Page.xaml.cs中使用代码设置HeadersContainer的一些属性。如果这些属性可以直接在页面XAML代码中设置,或者如果根本不需要填写这些属性,那会更容易。

HeadersContainer ContentTemplate和DataContext属性

当我们的GridBody显示主体headersContainer时,ContentTemplateDataContext属性在HandyContainerGetHeadersContainer方法中设置。当我们使用顶部headersContainer时,不会调用GetHeadersContainer方法,我们必须自己设置这些属性的值。然而,ContentTemplate的值是GridBodyItemTemplate值。因此,一旦设置了GridBody属性,就可以知道它。DataContext属性值可以从Content属性设置。

让我们修改HeadersContainer类,以便自动填充ContentTemplate值。

internal HandyContainer _GridBody
{
    get { return gridBody; }
    set
    {
        if (gridBody != value)
        {
            if (gridBody != null)
               RemoveGridBody();
                                    
            gridBody = value;

            if (gridBody != null)
                this.ContentTemplate = gridBody.ItemTemplate;

            if (isLoaded && isTemplateApplied)
                PrepareGridBody();
        }
    }
}

让我们还重写HeadersContainerOnContentChanged方法,以便在内容更改后立即填充DataContext

protected override void OnContentChanged(object oldContent, object newContent)
{
    base.OnContentChanged(oldContent, newContent);
    this.DataContext = newContent;
}

现在让我们从Page构造函数中删除DataContextContentTemplate设置。

public Page()
{
    InitializeComponent();

    CreateData();
    MyGridBody.ItemsSource = countryCollection;

    CountryHeaders.GridBody = MyGridBody;
    CountryHeaders.SourceLevel = 1;
    CountryHeaders.Content = new Country(" ", " ");
    
    RegionHeaders.GridBody = MyGridBody;
    RegionHeaders.SourceLevel = 2;
    RegionHeaders.Content = new StateProvince(" ", " ", " ");
    
    PersonHeaders.GridBody = MyGridBody;
    PersonHeaders.SourceLevel = 3;
    PersonHeaders.Content = new Person(" ", " ", " ", " ", " ", " ", " ", " ", 0, true);
}

HeadersContainer GridBody属性

让我们首先将GridBody属性替换为DependencyProperty。该属性将保存GridBodyHandyContainer的名称,而不是GridBodyHandyContainer本身。

public class HeadersContainer : GContentControl
{
    public static readonly DependencyProperty FullIndentationProperty;
    public static readonly DependencyProperty IndentationProperty;
    public static readonly DependencyProperty SourceLevelProperty;
    public static readonly DependencyProperty GridBodyProperty;


    private bool isInReadOnlyChange;

    static HeadersContainer()
    {
        FullIndentationProperty = DependencyProperty.Register("FullIndentation", 
           typeof(double), typeof(HeadersContainer), 
           new PropertyMetadata(new PropertyChangedCallback(OnFullIndentationChanged)));
        IndentationProperty = DependencyProperty.Register("Indentation", 
           typeof(double), typeof(HeadersContainer), 
           new PropertyMetadata(new PropertyChangedCallback(OnIndentationChanged)));
        SourceLevelProperty = DependencyProperty.Register("SourceLevel", 
           typeof(int), typeof(HeadersContainer), 
           new PropertyMetadata(1, new PropertyChangedCallback(OnSourceLevelChanged)));
        GridBodyProperty = DependencyProperty.Register("GridBody", 
           typeof(string), typeof(HeadersContainer), 
           new PropertyMetadata(new PropertyChangedCallback(OnGridBodyChanged)));        
    }
    ...

    public string GridBody
    {
        get { return (string)GetValue(GridBodyProperty); }
        set { SetValue(GridBodyProperty, value); }
    }

    private static void OnGridBodyChanged(DependencyObject d, 
            DependencyPropertyChangedEventArgs e)
    {
        (d as HeadersContainer)._OnGridBodyChanged((string)e.NewValue);
    }

    private void _OnGridBodyChanged(string newValue)
    {
        FindAndApplyGridBodyName(newValue);
    
    }

    private void FindAndApplyGridBodyName(string gridBodyName)
    {
        if (String.IsNullOrEmpty(gridBodyName))
            return;
    
        HandyContainer foundGridBody = this.FindName(gridBodyName) as HandyContainer;
        if (foundGridBody != null)
            _GridBody = foundGridBody;
    }

我们还需要更改HeadersContainer_LoadedOnApplyTemplate方法,以便在适当的时候调用FindAndApplyGridBodyName方法。

public override void OnApplyTemplate()
{
    base.OnApplyTemplate();

    ...

    if (isLoaded)
    {
        FindAndApplyGridBodyName(this.GridBody);
        if (gridBody != null)
            PrepareGridBody();
    }
}

void HeadersContainer_Loaded(object sender, RoutedEventArgs e)
{
    isLoaded = true;
    if (isTemplateApplied)
    {
        FindAndApplyGridBodyName(this.GridBody);
        if (gridBody != null)
            PrepareGridBody();

    }
}

让我们从Page.xaml.cs文件中删除CountryHeadersRegionHeadersPersonHeadersGridBody属性设置,并将此属性的值设置为我们GridBody项目的Page.xaml文件的XAML中。

<o:HeadersContainer x:Name="CountryHeaders" g:GDockPanel.Dock="Top" GridBody="MyGridBody" />
<o:HeadersContainer x:Name="RegionHeaders" g:GDockPanel.Dock="Top" GridBody="MyGridBody" />
<o:HeadersContainer x:Name="PersonHeaders" g:GDockPanel.Dock="Top" GridBody="MyGridBody" />

如果我们现在启动应用程序,顶部标题将继续按预期工作。

HeadersContainer SourceLevel属性

SourceLevel属性已经是DependencyProperty。我们可以直接在Page.xaml文件的XAML中设置其值。

让我们从Page.xaml.cs文件中删除CountryHeadersRegionHeadersPersonHeadersSourceLevel属性设置,并将这些属性的值设置在我们GridBody项目的Page.xaml文件的XAML中。由于SourceLevel的默认值为1,因此无需为CountryHeaders设置其值。

<o:HeadersContainer x:Name="CountryHeaders" 
   g:GDockPanel.Dock="Top" GridBody="MyGridBody"/>
<o:HeadersContainer x:Name="RegionHeaders" 
   g:GDockPanel.Dock="Top" GridBody="MyGridBody" SourceLevel="2"/>
<o:HeadersContainer x:Name="PersonHeaders" 
   g:GDockPanel.Dock="Top" GridBody="MyGridBody" SourceLevel="3"/>

HeadersContainer Content属性

无法在页面的XAML中填充Content属性(应该可以,但这会使操作比仅在page.xaml.cs文件中设置它复杂得多)。

我们可以对代码进行更改,以便网格自行查找要设置在Content属性中的值。毕竟,网格可以访问ItemsSource属性!它只需要“扫描”ItemsSource寻找满足条件的元素,然后将该元素放入Content属性。

然而,实现这样的功能可能会对性能产生严重影响。当处理远程服务器上的数据时(这是数据网格的主要目的),大多数时候我们不希望一次性加载填充网格的所有数据。相反,我们将仅从远程服务器加载集合的一部分元素,并在显示在网格中时按需填充元素(事情比这更复杂,这个主题应该是另一个教程的一部分,但我们在这里保持简单)。网格无法知道在哪里可以找到集合中满足Content属性值要求的元素。因此,网格必须扫描整个ItemsSource集合才能找到一个。如果正确的元素位于集合的末尾,这意味着网格将使我们的所有数据从远程服务器传输过来才能找到一个可接受的元素!这是一个不可容忍的缺点。

因此,即使市场上有很多网格都有类似的功能,我们也不会在我们的网格中实现这样的功能。

构建自己的Silverlight数据网格:第3步 - CodeProject - 代码之家
© . All rights reserved.