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






4.07/5 (6投票s)
为数据网格添加标题。
1. 引言
教程概述
本文是关于如何使用Silverlight和GOA Toolkit构建功能齐全的数据网格的教程的第三部分。
在本教程的第一部分,我们介绍了创建只读网格主体的方法。在第二部分,我们重点介绍了编辑和验证功能的实现。在本第三部分,我们将重点关注网格的标题。
在本文中,我们将假设您已阅读完前两部分教程。如果尚未阅读,我们强烈建议您这样做。您可以在这里访问它们。
入门
本教程是使用GOA Toolkit 2009 Vol1 build 289编写的。如果您在本文章发布前已实现了教程的第一步和第二步,您可能需要升级。请确保您使用的是GOA Toolkit 2009 Vol1 build 289或更高版本。
请确保您已在计算机上安装了此版本或更高版本(www.netikatech.com/downloads)。
目标
在前两个教程中,我们构建了网格的主体部分。我们构建的网格主体具有数据网格的所有基本功能(单元格、导航、单元格编辑等)。已实现了一些高级功能(虚拟模式、DataTemplate等)。尽管如此,在我们能够将标题与显示的单元格关联起来之前,我们的数据网格将无法使用。
这就是第三个教程的目的。
由于我们的网格可以显示分层数据(节点和子节点),因此我们必须能够将标题与网格的任何级别关联起来。我们将以两种不同的方式实现这一点:
- 允许在网格内部每个子集合的顶部显示标题,
- 并允许在网格顶部显示标题。
在第一种情况下,网格的外观将如下所示:
在第二种情况下,标题将显示在网格的顶部。
我们也允许实现介于两者之间的所有替代方案:
- 在网格主体内部显示部分标题,在外部显示部分标题。
- 仅根据预定义的条件或规则显示顶部标题。
- ...
当显示分层数据时,用户有时会觉得显示混乱。他们可能难以理解层次结构的各个级别。为了使分层数据更易读,我们将允许我们的网格在每个级别的顶部显示一个标题。如果您查看上面的两张图片,您可以看到在标题顶部显示的“国家”、“地区”和“人员”等标题。标题和标签并非必需。我们可以选择显示它们,也可以不显示。我们还可以选择仅显示标题而不显示标签,或反之亦然。
我们还将实现允许用户调整标题大小所需的功能。
超出范围
我们不讨论通过单击行标题来对网格数据进行排序的可能性。此功能更多地与数据相关,而不是与标题相关。添加一个在用户单击标题时显示向上箭头或向下箭头的视觉状态并不难。更困难的是将相应的排序规则应用于底层数据。这可能是另一个教程的主题。
2. 准备网格
重构网格的显示
我们的数据网格能够显示大量复杂的分层数据。我们必须确保数据以用户最易读的方式呈现。在网格主体中添加标题将增加复杂性。因此,在开始实现标题之前,让我们稍微改变一下行显示方式,使网格更具吸引力。
隔行变色
当我们将网格主体属性AlternateType
设置为“Items
”值时,每隔一行将以交替颜色显示背景。

此功能旨在提高网格的可读性。然而,交替颜色过于鲜艳,使得网格难以阅读。
在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
样式,并对其进行了少量增强,使其符合我们的需求。结果样式在节点展开和折叠时显示不同的背景。
我们将删除此行为,因为它在像网格这样的复杂控件中使用时会增加混乱。
此外,我们将默认Indentation
值增加到30,并更改ValidElement
和FocusElement
的边距,使它们更易读。
找到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
”。
让我们使用这些状态在项的顶部显示一个空格,当该项是新级别的第一个项时。
在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
应用类似的更改,使其更易读。我们将修改SelectedVisual
、ReflectVisual
、ValidElement
和FocusVisual
的边距。
<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
对于每个国家和地区,网格必须显示总费率,即该国家或地区所有员工费率的总和。
让我们首先修改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文件中修改GridBody
的ItemTemplate
属性:
<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. 添加标题
引言
当显示分层数据时,用户有时会觉得显示混乱。他们可能难以理解层次结构的各个级别。为了使分层数据更易读,我们将允许我们的网格在每个级别的顶部显示一个标题。
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
部分绘制一张图,它看起来会是这样:
ItemTitle
必须显示在ContainerItem
内容的正上方。它必须位于节点展开按钮的右侧。因此,插入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,标题显示在层次结构的第一个和第二个级别,但未在根级别显示。
让我们将这两个依赖项属性添加到我们的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
属性。此属性将用作ItemTitle
的Source
。
public string Title
{
get { return "Persons"; }
}
让我们对Country
类做同样的处理……
public string Title
{
get { return "Countries"; }
}
……以及StateProvince
类:
public string Title
{
get { return "Regions"; }
}
让我们还修改GridBody项目的Page.xaml文件,并为HandyContainer
的ItemTitleSource
属性提供一个值:
<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
状态和父HandyContainer
的ItemTitleStartDepth
属性值来决定ItemTitle
的可见性。ApplyHeadingVisibility
方法将调用一个名为ApplyTitleContent
的方法,该方法将根据父HandyContainer
的ItemTitleSource
值填充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
正确显示,我们必须重写ContainerItem
的OnNodeLevelActionChanged
和OnRefreshed
方法。
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
ItemTitleSource
和ItemTitleStartDepth
的实现方式是,当它们的值被修改时,网格主体内显示的标题不会自动更新。为了实现此功能,我们应该跟踪ItemTitleSource
和ItemTitleStartDepth
值的更改。这可以在我们网格的增强版本中实现。
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
是包含它的项的一部分。
让我们捕获ItemTitle
的MouseEnter
和MouseLeave
事件以消除此行为。
让我们首先修改ContainerItem
的OnApplyTemplate
方法。
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_MouseEnter
和headers_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. 主体标题
引言
我们将“主体标题”称为显示在网格主体内部的标题。显示在网格顶部的标题将称为“顶部标题”。
创建网格的过程可分为三个步骤:
- 将
HandyContainer
添加到我们的XAML页面并相应设置其属性。 - 编写
HandyContainer
的ItemTemplate
,以便选择行内单元格(即项)的显示方式。 - 填充
HandyContainer
的ItemsSource
属性。
我们的要求是:我们不想添加单独的步骤来定义主体标题。标题的布局方式必须与行单元格相同。
例如,如果HandyContainer
的ItemTemplate
显示网格行如下:
那么标题必须以同样的方式显示:
因此,用于构建网格行的ItemTemplate
也将用于构建标题。
更新Cell类
引言
附加到GridBody
的HandyContainer
的ItemTemplate
将用于构建行和标题。因此,在定义HandyContainer
的ItemTemplate
时,必须能够定义标题的一些行为。
为了实现此功能,我们将向Cell
类添加两个属性:
Header
:此属性允许定义附加到单元格的标题的内容。换句话说,它允许定义在标题内显示什么(文本、图像等)。UserResizeType
:用户调整标题大小(因此也调整单元格大小)的方式。可能的值为None
、Right
和Bottom
。
创建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
类的Header
和UserResizeType
属性:
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
组成,该控件持有标题。
让我们首先创建一个空的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);
}
}
}
HeadersContainer
是ContentControl
。我们选择继承自ContentControl
,因为稍后我们将把HandyContainer
的ItemTemplate
应用于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
已经处理了。
让我们举个例子。假设我们有一个应用程序显示以下网格:
假设用户按下“page-down”键并显示以下行:
如果我们不注意我们的操作,在上述情况下,当用户按下“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
内部添加了一个名为HeadersCanvas
的GCanvas
。
Scroller
是一个允许滚动Panel
内容的控件。Scroller
(GOA控件)和ScrollViewer
(Silverlight控件)的区别在于,scroller允许滚动实现IScrollerOperator
接口的面板内容。如果我们将一个面板放在ScrollViewer
中,当用户滚动时,面板会相应移动。如果我们将其放在Scroller
中,当用户滚动时,面板不会移动,但会告诉面板移动其内容。我们在这里不深入细节,只说明Scroller
被用作ScrollViewer
的替代品,主要是因为它能够支持虚拟模式。
由于我们在Scroller
中添加了HeadersCanvas
,因此Scroller
现在包含多个面板。因此,我们必须告诉Scroller
当用户使用控件的滚动条时,它将滚动哪个面板。我们通过将Scroller
的ScrollOperator
属性填充为GItemPresenter
的名称来做到这一点。
注意:GItemsPresenter
不是面板。尽管如此,它的目的是“成为”HandyContainer
的ItemHost
。它将被HandyContainer
的ItemsPanelModel
属性中定义的面板实例“替换”。因此,Scroller
会将GItemPresenter
视为面板。
实现HeadersContainer缓存
我们将向HandyContainer
添加两个方法:GetHeadersContainer
和CacheHeadersContainer
。
GetHeadersContainer
方法允许获取一个“空闲”的HeadersContainer
。空闲的HeadersContainer
是一个当前未使用的(未显示的)HeadersContainer
。该方法将获取一个空闲的HeadersContainer
,要么从缓存中获取,要么在缓存为空时创建一个新的。
CacheHeadersContainer
方法将缓存一个不再使用的HeadersContainer
。
所有HeadersContainer
都不包含相同的标题。例如,如果我们的网格显示以下层次结构:
Countries
Regions
Employees
那么国家标题将与人员标题不同。
我们将假定HeadersContainer
可以被重用:
- 如果链接到项的元素的类型与创建
HeadersContainer
时链接到项的元素的类型相同。换句话说,如果一个HeadersContainer
链接到显示人员数据的项,它就可以成为链接到显示国家数据的其他项的HeadersContainer
。相反,它不能成为链接到显示国家数据的项的HeadersContainer
(否则,HeadersContainer
需要重建,这个过程将需要很长时间)。 - 如果链接到标题的项与创建
HeadersContainer
时链接到项的项在相同的层次结构级别。
没有图片很难理解。让我们看看下面的两张图片:
第一张图片中用红色矩形圈出的标题可以重用来显示第二张图片中用红色矩形圈出的标题。
相反,第一张图片中用绿色矩形圈出的标题不能用来显示第二张图片中用红色矩形圈出的标题。
在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
持有,而是由HandyContainer
的HeadersCanvas
持有。尽管如此,我们仍然需要修改Container_RowNodeStyle
,以便在HeadersContainer
显示的位置留出一个空位。
为了能够构建这个空位,让我们在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
方法调用父HandyContainer
的GetHeadersContainer
方法以从缓存中获取HeadersContainer
。RemoveHeadersContainer
方法调用父HandyContainer
的CacheHeadersContainer
方法将HeadersContainer
放回缓存。
HeadersContainer
需要位于正确的位置,并且其宽度必须根据项的宽度设置。这在多个地方完成:
- 在
InitializeHeadersContainer
中,我们初始化了HeadersContainer
的位置和大小。 - 在
OnArrange
方法中,我们计算了HeadersContainer
的顶部位置。当项被HandyContainer
的ItemsHost
排列(移动或调整大小时)时,会调用OnArrange
方法。 - 在
ArrangeOverride
方法中。当Silverlight必须排列项时,它会调用ArrangeOverride
方法。
当标题不再可见时,RemoveHeadersContainer
方法由ApplyHeadingVisibility
调用。
但是,从ContainerItem
内部调用RemoveHeadersContainer
还不够。如果一个ContainerItem
从ItemsHost
中删除,ContainerItem
不知道此操作。但是,在这种情况下,我们也需要调用RemoveHeadersContainer
方法。幸运的是,HandyContainer
类有一个ClearContainerForItemOverride
方法,该方法在每次发生此类事件时都会被调用。让我们重写HandyContainer
的ClearContainerForItemOveride
方法:
protected override void ClearContainerForItemOverride(
DependencyObject element, object item)
{
((ContainerItem)element).RemoveHeadersContainer(this);
base.ClearContainerForItemOverride(element, item);
}
InitializeHeadersContainer
方法考虑到了ContainerItem
可能被“重用”的事实(请参阅本教程以上关于此内容的说明)。如果HeadersContainer
已存在并链接到正确类型的数据,并链接到正确的层次结构级别,则会重用它。否则,将创建一个新的。
为了检查现有HeadersContainer
链接到的层次结构级别,InitializeHeadersContainer
方法会检查HeadersContainer
的SourceLevel
属性的值。
我们还没有创建这个属性。让我们将其添加到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); }
}
}
让我们在HandyContainer
的GetHeadersContainer
方法中填充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;
}
为了能够观察HeadersContainer
s,我们暂时用“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;
}
}
}
接下来要做的是向类添加FullIndentation
和Indentation
属性。FullIdentation
属性值将从Indentation
和SourceLevel
属性值计算得出。它在样式中使用,以确保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;
}
我们还需要修改HandyContainer
的GetHeadersContainer
方法,以便用正确的值填充HeadersContainer
的Indentation
属性:
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
之前,有时发生在之后,我们必须使用isLoaded
和isTemplateApplied
标志来跟踪对方法和事件的调用。
让我们向我们的类添加一个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();
}
}
我们仍然需要修改HandyContainer
的GetHeadersContainer
方法,以便填充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
应用于HeadersContainer
的ContentTemplate
属性。 - 将链接到的项之一的
Content
应用于HeadersContainer
的Content
属性。
因此,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
的项之一的内容填充。
让我们还修改HeadersContainer
的PrepareGridBody
方法,以初始化HeadersContainer
的ContentTemplate
和Content
。
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;
}
}
让我们修改HandyContainer
的GetHeadersContainer
方法,以便使用项的Content
和ContentTemplate
填充HeadersContainer
的Content
和ContentTemplate
:
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
方法。该方法将调用父HeadersContainer
的RegisterCell
方法(如果存在)。
private void RegisterCell()
{
HeadersContainer headersContainer =
HeadersContainer.GetParentHeadersContainer(this);
if (headersContainer != null)
headersContainer.RegisterCell(this);
}
RegisterCell
方法将在单元格完全加载后(已加载并应用了模板)被调用。由于在Silverlight中无法预测Loaded
事件和OnApplyTemplate
方法的调用顺序,我们将必须使用isLoaded
和isTemplateApplied
标志来跟踪对方法和事件的调用。
让我们首先在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;
}
如果我们现在启动应用程序,我们可以看到标题显示在headersContainers
s中的正确位置。
Header样式
我们需要增强标题的样式,以使标题更具吸引力。
同时,我们将在标题样式中添加一个WindowsSizer
。它将允许用户调整标题的大小。标题中的WindowSizer
的VerticalSizeMode
和HorizontalSizeMode
将绑定到标题的VerticalSizeMode
和HorizontalSizeMode
属性。
让我们将这两个属性添加到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;
}
当调用HeadersContainer
的RegisterCell
方法时,此属性将被填充一个值。
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
类添加SizerHeight
和SizerWidth
方法。
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;
}
}
标题的VerticalSizeMode
和HorizontalSizeMode
属性可以从UserResizeType
属性计算得出。
因此,让我们将VerticalSizeMode
和HorizontalSizeMode
属性设为只读,并根据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;
}
}
让我们修改HeadersContainer
的RegisterCell
方法,以初始化标题的UserResizeType
、SizerHeight
和SizerWidth
属性:
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;
...
}
如果我们现在启动应用程序,我们可以看到我们可以调整标题的大小(默认情况下是右侧)。然而,当调整标题大小时,单元格的大小不会被修改。
保持单元格大小与标题大小同步
为了在调整标题大小时调整单元格大小,我们需要在标题调整大小时得到通知。
因此,让我们“连接”到标题中的WindowSizer
的UserResizing
和UserResizeComplete
事件,并在Header
类中添加UserResizing
和UserResizeComplete
事件。
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);
}
让我们在HeaderContainer
的RegisterCell
方法中“连接”到标题的UserResizing
和UserResizeComplete
事件。
因为,当调整一个标题大小时,所有“链接”到该标题的单元格都必须调整大小,这个操作应该在HandyContainer
(gridBody
)内部进行。这是可以访问所有单元格的地方。因此,当调整标题大小时,我们将调用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
属性值匹配 - 并且由位于与
HeadersContainer
的SourceLevel
对应的层次结构级别的项持有 - 并且链接到与
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
类添加SetCellWidth
、SetCellHeight
、GetCellWidth
和GetCellHeight
方法。
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;
}
我们需要在标题调整大小时尽快调用SetCellWidth
和SetCellHeight
方法(在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
方法,以考虑HandyContainer
的GetCellWidth
和GetCellHeight
方法提供的值:
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;
...
}
最后,让我们根据HandyContainer
的GetCellWidth
和GetCellHeight
方法返回的值初始化单元格的大小:
让我们向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
方法时会失败。如果我们深入研究,我们可以看到ContainerItem
的GetParentContainerItem
方法返回了一个意外的null值。
为什么一个单元格没有父ContainerItem
?
事实上,单元格是由HeadersContainer
在填充其ContentTemplate
和Content
属性时生成的单元格之一。由于单元格是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;
}
}
}
如果我们启动应用程序并调整标题大小,我们可以看到单元格和标题的大小现在保持同步。
注意
当使用SetCellWidth
或SetCellHeight
方法保存了单元格宽度或高度时,该值会一直保存在内存中,直到相应的HandyContainer
被销毁。这意味着,如果修改了ContainerItem
的ItemsSource
的值以在网格中显示不同类型的数据,则脏单元格的宽度和高度值会保留在内存中。在网格的增强版本中,当修改ItemsSource
值时,我们应该清除cellWidths
和cellHeights
字典的内容。
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
链接到我们的GridBody
,HeaderContainer
必须有一个允许这样做的公共属性。
让我们向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;
}
注意,在上面的代码中,我们用空格值填充了Country
、StateProvince
和Person
类实例的几乎所有字段值。由于这些实例的数据未显示(单元格被替换为标题),我们可以使用任何值来填充这些实例的字段。然而,最好避免使用null或空值。标题的大小是根据它们替换的单元格的大小计算的。包含空值或null值的单元格可能具有意外的大小。
让我们启动我们的应用程序并观察我们的更改。
标题显示在顶部,并且它们按预期工作。然而,我们不得不在Page.xaml.cs中使用代码设置HeadersContainer
的一些属性。如果这些属性可以直接在页面XAML代码中设置,或者如果根本不需要填写这些属性,那会更容易。
HeadersContainer ContentTemplate和DataContext属性
当我们的GridBody
显示主体headersContainer
时,ContentTemplate
和DataContext
属性在HandyContainer
的GetHeadersContainer
方法中设置。当我们使用顶部headersContainer
时,不会调用GetHeadersContainer
方法,我们必须自己设置这些属性的值。然而,ContentTemplate
的值是GridBody
的ItemTemplate
值。因此,一旦设置了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();
}
}
}
让我们还重写HeadersContainer
的OnContentChanged
方法,以便在内容更改后立即填充DataContext
。
protected override void OnContentChanged(object oldContent, object newContent)
{
base.OnContentChanged(oldContent, newContent);
this.DataContext = newContent;
}
现在让我们从Page
构造函数中删除DataContext
和ContentTemplate
设置。
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。该属性将保存GridBody
的HandyContainer
的名称,而不是GridBody
的HandyContainer
本身。
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_Loaded
和OnApplyTemplate
方法,以便在适当的时候调用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文件中删除CountryHeaders
、RegionHeaders
和PersonHeaders
的GridBody
属性设置,并将此属性的值设置为我们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文件中删除CountryHeaders
、RegionHeaders
和PersonHeaders
的SourceLevel
属性设置,并将这些属性的值设置在我们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
集合才能找到一个。如果正确的元素位于集合的末尾,这意味着网格将使我们的所有数据从远程服务器传输过来才能找到一个可接受的元素!这是一个不可容忍的缺点。
因此,即使市场上有很多网格都有类似的功能,我们也不会在我们的网格中实现这样的功能。