总想花些时间来好好总结一下TreeView这个WPF控件,今天来通过下面的这几个例子来好好总结一下这个控件,首先来看看一个常规的带虚线的TreeView控件吧,在介绍具体如何完成之前首先来看看最终实现的效果图吧!

  然后我们来具体分析一下这个是怎样去实现的?

1 修改TreeView的模板层

其实TreeView中最重要的就是TreeViewItem项,这个决定了最终TreeView的展现方式,另外就是TreeView每展开子项时前面的ToggleButton的样式了,因为默认的TreeView样式前面是一个三角形,那么我们先从整个TreeView的样式开始说起吧!

 <Style TargetType="{x:Type TreeViewItem}">
<Setter Property="Background" Value="Transparent"/>
<!--此处设置TreeViewItem的绑定样式-->
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"></Setter>
<Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
<Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
<Setter Property="Padding" Value="1,0,0,0"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="FocusVisualStyle" Value="{StaticResource TreeViewItemFocusVisual}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TreeViewItem}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="19" Width="Auto"/>
<ColumnDefinition Width="150" MinWidth="150"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" MinHeight="22"/>
<RowDefinition />
</Grid.RowDefinitions>
<!-- Connecting Lines -->
<!-- Horizontal line -->
<Rectangle x:Name="HorLn" Margin="9,0,0,0" Height="1" Stroke="Green" SnapsToDevicePixels="True"/>
<!-- Vertical line -->
<Rectangle x:Name="VerLn" Width="1" Stroke="Green" Margin="0,0,1,0" Grid.RowSpan="2" SnapsToDevicePixels="true" Fill="White"/>
<ToggleButton x:Name="Expander"
Grid.Column="0"
Grid.Row="0"
ClickMode="Press"
IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}"
Style="{StaticResource ExpandCollapseToggleStyle}"/>
<Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
Grid.Column="1"
Padding="{TemplateBinding Padding}"
SnapsToDevicePixels="true">
<ContentPresenter x:Name="PART_Header"
ContentSource="Header"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
Width="250"/>
</Border>
<ItemsPresenter x:Name="ItemsHost"
Grid.ColumnSpan="2"
Grid.Column="1"
Grid.Row="1" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="false">
<Setter Property="Visibility" TargetName="ItemsHost" Value="Collapsed"/>
</Trigger>
<Trigger Property="HasItems" Value="false">
<Setter Property="Visibility" TargetName="Expander" Value="Hidden"/>
</Trigger>
<Trigger Property="IsSelected" Value="true">
<Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="true"/>
<Condition Property="IsSelectionActive" Value="false"/>
</MultiTrigger.Conditions>
<Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
</MultiTrigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="VirtualizingStackPanel.IsVirtualizing" Value="true">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<VirtualizingStackPanel/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>

  这里面重点讲述一下TreeViewItem的模板,首先TreeViewItem是有一个两行三列的Grid构成的,首先在第0行0列有两部分构成的,首先是注释部分已经写清楚了是Horizontal line 这个是每一个TreeViewItem的水平横线,实际上是一个高度为1的Rectangle,<Rectangle x:Name="HorLn" Margin="9,0,0,0" Height="1" Stroke="Green" SnapsToDevicePixels="True"/>,另外一部分是很重要的ToggleButton,这里是通过一个Style来表示的,Style="{StaticResource ExpandCollapseToggleStyle}"那么我们再具体看看这个ExpandCollapseToggleStyle的样式。

 <Style x:Key="ExpandCollapseToggleStyle" TargetType="ToggleButton">
<Setter Property="Focusable" Value="False"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Grid Width="15" Height="13" SnapsToDevicePixels="True">
<!-- Rectangle 9x9 pixels -->
<Rectangle Width="15" Height="15" Stroke="#919191" SnapsToDevicePixels="true">
<Rectangle.Fill>
<LinearGradientBrush EndPoint="0.5,2" StartPoint="0.5,0">
<GradientStop Color="#00a3d9" Offset="0"/>
<GradientStop Color="#00a3d9" Offset="0.5"/>
<GradientStop Color="#00a3d9" Offset="1"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<!-- 画一个垂直方向的直线 -->
<Rectangle x:Name="ExpandPath" Width="2" Height="8" Stroke="Black" SnapsToDevicePixels="true"/>
<!-- 画一个水平方向的直线 -->
<Rectangle Width="8" Height="2" Stroke="Black" SnapsToDevicePixels="true"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Visibility" TargetName="ExpandPath" Value="Collapsed"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

  这个其实也比较简单就是在一个Grid里面放了两个Rectangle,并通过绑定IsChecked属性来改变垂直的那条直线的Visibility属性来完成最后的效果。介绍完了这一部分,我们再看看在TreeViewItem的模板中剩下的部分,在Grid的第0行第一列是用来显示每一个TreeViewItem的标题的内容,总体的结构是一个Border里面嵌套了一个ContentPresenter最终来呈现TreeViewItem的Header,这里面重点来介绍一下 ContentSource="Header" 这个属性非常重要,我们知道我们在给Header赋值时就会呈现该内容,当然赋值的内容可以是文字也可以是一个对象,如果是对象的话还需要通过写一个DataTemplate来呈现这个对象,我们来看看MSDN上的解释:获取或设置要在自动命名别名过程中使用的基名称。同时还给出了一个完整的例子,请参考下面的代码。

下面的示例演示一个用于样式 HeaderedContentControl 演示的用法 ContentSource 属性︰

<Style TargetType="HeaderedContentControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type HeaderedContentControl}">
<StackPanel>
<Grid>
<Rectangle Stroke="{TemplateBinding Background}"/>
<ContentPresenter ContentSource="Header"/>
</Grid>
<Grid>
<Rectangle Fill="{TemplateBinding Background}"/>
<ContentPresenter ContentSource="Content"/>
</Grid>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

  这里使用这个例子再合适不过了,因为TreeViewItem就是直接从HeaderedContentControl继承过来的。

在介绍了上面这些外再看看Grid中第1行第2、3两列的模板内容吧! <ItemsPresenter x:Name="ItemsHost"  Grid.ColumnSpan="2"                                   Grid.Column="1"  Grid.Row="1" /> 这个ItemsPresenter 主要是来呈现当前TreeViewItem的子项的,这个也非常重要的,否则是不会显示子项内容的,按照这样去分析整个View层的模板就都已经到位了,另外再看这些代码中两个容易被忽视的小细节,一个是FocusVisualStyle它使用的是TreeViewItemFocusVisual,这个表示当前TreeView获得焦点时呈现的状态,MSDN的解释如下:获取或设置一个属性,该属性允许自定义此元素在捕获到键盘焦点时要应用于此元素的外观、效果或其他样式特征。(继承自 FrameworkElement。)我们再来看看具体的样式代码。

  <Style x:Key="TreeViewItemFocusVisual">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Rectangle/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

  另外一个重要的部分就是TreeView的Style.Triggers属性,当TreeView的 VirtualizingStackPanel.IsVirtualizing设置为True时,为ItemsPanel的模板设置为VirtualizingStackPanel,至于VirtualizingStackPanel到底有哪些作用,这些又是一个很宏大的内容,我们来看看几篇介绍得比较好的博客及文章。

2  TreeView控件绑定数据

  前面的一部分内容只是单纯去修改TreeView的模板,从而决定最终展示的形式,但是最终展示哪些内容,如何展示就涉及到另外的一部分内容即:TreeView控件的数据绑定。在我们的DEMO中,我们使用级联的数据模板来展示最终的内容,关于级联的数据模板,这里也不再赘述,直接来看代码,然后做进一步的分析。  

    <TreeView x:Name="trvMenu" Background="LightGray">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type self:MyTreeViewItem}"
ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<Image Source="/TestTreeView;component/Images/connect.png"
Width="24"
Height="24"
Stretch="Fill">
</Image>
<TextBlock Text="{Binding TreeViewItemName}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>

  每一个TreeViewItem的最终呈现的方式是通过TreeView.ItemTemplate的模板来表现的,这里通过一个StackPanel来展示每个节点的内容,包括一个图片和一个TextBlock内容,如果有需要还可以为每个TreeViewItem前面加上CheckBox等控件,从而完成更加复杂的内容。

  最后面就是通过代码来给该TreeView绑定内容了,这里也贴出代码。这里直接在MainWindow.cs中添加相关的内容。

 private void InitDataSource()
{
MyTreeViewItem root = new MyTreeViewItem() { TreeViewItemName="Root"};
MyTreeViewItem childLevel1 = new MyTreeViewItem() { TreeViewItemName = "childLevel1" };
MyTreeViewItem childLevel1_1 = new MyTreeViewItem() { TreeViewItemName = "childLevel1_1" };
MyTreeViewItem childLevel1_1_1 = new MyTreeViewItem() { TreeViewItemName = "childLevel1_1_1" };
MyTreeViewItem childLevel1_1_1_1 = new MyTreeViewItem() { TreeViewItemName = "childLevel1_1_1_1" };
MyTreeViewItem childLevel1_1_1_2 = new MyTreeViewItem() { TreeViewItemName = "childLevel1_1_1_2" };
MyTreeViewItem childLevel1_1_1_3 = new MyTreeViewItem() { TreeViewItemName = "childLevel1_1_1_3" };
MyTreeViewItem childLevel1_1_1_4 = new MyTreeViewItem() { TreeViewItemName = "childLevel1_1_1_4" };
childLevel1_1_1.Children.Add(childLevel1_1_1_1);
childLevel1_1_1.Children.Add(childLevel1_1_1_2);
childLevel1_1_1.Children.Add(childLevel1_1_1_3);
childLevel1_1_1.Children.Add(childLevel1_1_1_4);
childLevel1_1.Children.Add(childLevel1_1_1); MyTreeViewItem childLevel1_2 = new MyTreeViewItem() { TreeViewItemName = "childLevel1_2" };
MyTreeViewItem childLevel1_2_1 = new MyTreeViewItem() { TreeViewItemName = "childLevel1_2_1" };
MyTreeViewItem childLevel1_2_2 = new MyTreeViewItem() { TreeViewItemName = "childLevel1_2_2" };
childLevel1_2.Children.Add(childLevel1_2_1);
childLevel1_2.Children.Add(childLevel1_2_2); childLevel1.Children.Add(childLevel1_1);
childLevel1.Children.Add(childLevel1_2); MyTreeViewItem childLevel2 = new MyTreeViewItem() { TreeViewItemName = "childLevel2" };
MyTreeViewItem childLevel2_1 = new MyTreeViewItem() { TreeViewItemName = "childLevel2_1" };
MyTreeViewItem childLevel2_2 = new MyTreeViewItem() { TreeViewItemName = "childLevel2_2" };
MyTreeViewItem childLevel2_3 = new MyTreeViewItem() { TreeViewItemName = "childLevel2_3" };
MyTreeViewItem childLevel2_4 = new MyTreeViewItem() { TreeViewItemName = "childLevel2_4" };
childLevel2.Children.Add(childLevel2_1);
childLevel2.Children.Add(childLevel2_2);
childLevel2.Children.Add(childLevel2_3);
childLevel2.Children.Add(childLevel2_4); root.Children.Add(childLevel1);
root.Children.Add(childLevel2); trvMenu.Items.Add(root); }

  这里面最重要的就是MyTreeViewItem对象了,这个是绑定到TreeViewItem中的最直接的数据源,对应 DataType="{x:Type self:MyTreeViewItem}" 这里面的内容,这里也贴出相关的代码。

public class MyTreeViewItem
{
private string _treeViewItemName;
public string TreeViewItemName
{
get { return _treeViewItemName; }
set { _treeViewItemName = value; }
}
private ObservableCollection<MyTreeViewItem> _children = new ObservableCollection<MyTreeViewItem>();
public ObservableCollection<MyTreeViewItem> Children
{
get
{
return _children;
}
set
{
if (value != _children)
{
_children = value;
}
} }
}

  这里需要说明一下,这种写法有些不规范,单由于仅仅是一个简单的DEMO,规范的写法是每一个属性都要带通知,及当前MyTreeViewItem类要实现INotifyPropertyChanged接口,并且在属性值改变时发出通知,从而通过View层去更新相应的内容。

另外需要注意的内容是 HierarchicalDataTemplate 的ItemsSource绑定的内容ItemsSource="{Binding Children}",这个Children代表的是当前节点的下一级别节点的数据源,第一级节点的数据源已经通过后台代码绑定好了,这里需要特别注意。

下一节我们将继续如何巧用TreeView的模板来实现更加多样的数据呈现方式,从而真正地去体现WPF的强大之处......

重写TreeView模板来实现数据分层展示(一)的更多相关文章

  1. 重写TreeView模板来实现数据分层展示(二)

    前面一片文章实现TreeView的基本的模板重写,那么照着这个思路,我们再来写一个稍稍复杂的TreeView ,其它的内容都和前面系列内容相似,还是和之前文章介绍的一样,首先看看做出的DEMO的最终样 ...

  2. springboot 使用model重定向到html模板,对数据进行展示

    1:使用springboot, ,html使用thymeleaf,nekohtml模板 在build.gradle中添加依赖 buildscript { repositories { mavenCen ...

  3. GIS案例学习笔记-CAD数据分层导入现有模板实例教程

    GIS案例学习笔记-CAD数据分层导入现有模板实例教程 联系方式:谢老师,135-4855-4328,xiexiaokui#qq.com 1. 原始数据: CAD数据 目标模板 2. 任务:分5个图层 ...

  4. 移动端基于HTML模板和JSON数据的JavaScript交互

    写本文之前,我正在做一个基于Tab页的订单中心: 每点击一个TAB标签,会请求对应状态的订单列表.之前的项目,我会在js里使用 +  连接符连接多个html内容: var html = ''; htm ...

  5. python操作三大主流数据库(14)python操作redis之新闻项目实战②新闻数据的展示及修改、删除操作

    python操作三大主流数据库(14)python操作redis之新闻项目实战②新闻数据的展示及修改.删除操作 项目目录: ├── flask_redis_news.py ├── forms.py ├ ...

  6. 学会这一招,小白也能使用数据可视化BI软件创建医院数据实时展示大屏

    灯果数据可视化BI软件是新一代人工智能数据可视化大屏软件,内置丰富的大屏模板,可视化编辑操作,无需任何经验就可以创建属于你自己的大屏.大家可以在他们的官网下载软件.   本文以医院数据实时展示大屏为例 ...

  7. 各种JS模板引擎对比数据(高性能JavaScript模板引擎)

    最近做了JS模板引擎测试,拿各个JS模板引擎在不同浏览器上去运行同一程序,下面是模板引擎测试数据:通过测试artTemplate.juicer与doT引擎模板整体性能要有绝对优势: js模板引擎 Ja ...

  8. Thymeleaf+SpringMVC,如何从模板中获取数据

    Thymeleaf+SpringMVC,如何从模板中获取数据 在一个典型的SpringMVC应用中,带@Controller注解的类负责准备数据模型Map的数据和选择一个视图进行渲染.这个模型Map对 ...

  9. 在GridControl表格控件中实现多层级主从表数据的展示

    在一些应用场景中,我们需要实现多层级的数据表格显示,如常规的二级主从表数据展示,甚至也有多个层级展示的需求,那么我们如何通过DevExpress的GridControl控表格件实现这种业务需求呢?本篇 ...

随机推荐

  1. Md5的生成

    1.使用hashlib包(一) import hashlib src = 'anthing' m1 = hash.new() m1.update(src) print (m1.hexdigest()) ...

  2. P1451 求细胞数量(广搜)

    题意:就是0把是所有细胞圈起来了.而被圈起来的是所有数字全部为一个细胞问有多少个这样的细胞.(mmp,我读半天题) 思路:广搜索.就是,0的话就不放入了,不为0的话,就进入队列,然后,再看它的4个方向 ...

  3. Python:Day15 函数

    函数参数补充: 还可以这样传参: def f(*args): print(args) f(*[1,3,4,5]) #输出结果:(1, 3, 4, 5) 注意这是一个元组 def f2(**kwargs ...

  4. .Net使用RabbitMQ详解 转载http://www.cnblogs.com/knowledgesea/p/5296008.html

    .Net使用RabbitMQ详解   序言 这里原来有一句话,触犯啦天条,被阉割!!!! 首先不去讨论我的日志组件怎么样.因为有些日志需要走网络,有的又不需要走网路,也是有性能与业务场景的多般变化在其 ...

  5. Spring Security(八):2.4.3 Project Modules

    In Spring Security 3.0, the codebase has been sub-divided into separate jars which more clearly sepa ...

  6. a,b,c为3个整型变量,在不引入第四个变量的前提下写一个算法实现 a=b b=c c=a?(异或解决值互换问题)

    package com.Summer_0424.cn; /** * @author Summer * a,b,c为3个整型变量,在不引入第四个变量的前提下写一个算法实现 a=b b=c c=a? */ ...

  7. C++模板参数类型(转载)

    实际上有三种类型模板参数:类型模板参数.无类型模板参数和模板模板参数(以模板作为模板的参数). .类型模板参数 类型模板参数是我们使用模板的主要目的.我们可以定义多个类型模板参数: template& ...

  8. Omi教程-组件通讯攻略大全

    组件通讯 Omi框架组建间的通讯非常遍历灵活,因为有许多可选方案进行通讯: 通过在组件上声明 data-* 传递给子节点 通过在组件上声明 data 传递给子节点 (支持复杂数据类型的映射) 父容器设 ...

  9. UWP 自定义控件:了解模板化控件 系列文章

    UWP自定义控件的入门文章 [UWP 自定义控件]了解模板化控件(1):基础知识 [UWP 自定义控件]了解模板化控件(2):模仿ContentControl [UWP 自定义控件]了解模板化控件(2 ...

  10. IT江湖--这个冬天注定横尸遍野

    今年江湖大事繁起,又至寒冬,冻的不仅是温度,更是人心. 这两天上班途中看到多个公众号和媒体发了很多 "XXX公司裁员50%" 等等诸如此类的文章,也真是撼动人心.寒冬,比以往来的更 ...