一直都想要個樹形展開的表格 像這樣的
今天心血來潮就簡單做了個TreeGrid 喜歡的同學可以下載下去自己研究下,其實也比較簡單主要就是TreeView TreeViewItem再配合GridViewHeaderRowPresenter、GridViewRowPresenter、GridViewColumnCollection定制style基本上就可以實現以上效果
本文中涉及大量模板,綁定等知識,如果剛剛入門還沒有了解以上知識的同學可以先補下課,然後再來看,以免浪費時間。
閒話不多說,直接上代碼:
前台代碼 MainWindow.xaml
[csharp]
<Window x:Class="TreeGrid.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TreeGrid"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:LevelToMarginConverter x:Key="LevelToIndentConverter"/>
<Style x:Key="ExpandCollapseToggleStyle"
TargetType="{x:Type ToggleButton}">
<Setter Property="Focusable"
Value="False"/>
<Setter Property="Width"
Value="19"/>
<Setter Property="Height"
Value="13"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Width="19"
Height="13"
Background="Transparent">
<Border Width="9"
Height="9"
BorderThickness="1"
BorderBrush="#FF7898B5"
CornerRadius="1"
SnapsToDevicePixels="true">
<Border.Background>
<LinearGradientBrush StartPoint="0,0"
EndPoint="1,1">
<LinearGradientBrush.GradientStops>
<GradientStop Color="White"
Offset=".2"/>
<GradientStop Color="#FFC0B7A6"
Offset="1"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Border.Background>
<Path x:Name="ExpandPath"
Margin="1,1,1,1"
Fill="Black"
Data="M 0 2 L 0 3 L 2 3 L 2 5 L 3 5 L 3 3
L 5 3 L 5 2 L 3 2 L 3 0 L 2 0 L 2 2 Z"/>
</Border>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked"
Value="True">
<Setter Property="Data"
TargetName="ExpandPath"
Value="M 0 2 L 0 3 L 5 3 L 5 2 Z"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<DataTemplate x:Key="CellTemplate_Name">
<DockPanel>
<ToggleButton x:Name="Expander"
Style="{StaticResource ExpandCollapseToggleStyle}"
Margin="{Binding Level,Converter={StaticResource LevelToIndentConverter}}"
IsChecked="{Binding Path=IsExpanded,
RelativeSource={RelativeSource
AncestorType=
{x:Type TreeViewItem}}}"
ClickMode="Press"/>
<TextBlock Text="{Binding Name}"/>
</DockPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=HasItems,
RelativeSource={RelativeSource
AncestorType={x:Type TreeViewItem}}}"
Value="False">
<Setter TargetName="Expander"
Property="Visibility"
Value="Hidden"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
<GridViewColumnCollection x:Key="gvcc">
<GridViewColumn Header="Name"
CellTemplate="{StaticResource CellTemplate_Name}" />
<GridViewColumn Header="JobTitle"
DisplayMemberBinding="{Binding JobTitle}" Width="60"/>
<GridViewColumn Header="Age"
DisplayMemberBinding="{Binding Age}" Width="60" />
<GridViewColumn Header="Sex"
DisplayMemberBinding="{Binding Sex}" Width="60"/>
</GridViewColumnCollection>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TreeViewItem}">
<StackPanel>
<Border Name="Bd"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}">
<GridViewRowPresenter x:Name="PART_Header"
Content="{TemplateBinding Header}"
Columns="{StaticResource gvcc}" />
</Border>
<ItemsPresenter x:Name="ItemsHost" />
</StackPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded"
Value="false">
<Setter TargetName="ItemsHost"
Property="Visibility"
Value="Collapsed"/>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="HasHeader"
Value="false"/>
<Condition Property="Width"
Value="Auto"/>
</MultiTrigger.Conditions>
<Setter TargetName="PART_Header"
Property="MinWidth"
Value="75"/>
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="HasHeader"
Value="false"/>
<Condition Property="Height"
Value="Auto"/>
</MultiTrigger.Conditions>
<Setter TargetName="PART_Header"
Property="MinHeight"
Value="19"/>
</MultiTrigger>
<Trigger Property="IsSelected"
Value="true">
<Setter TargetName="Bd"
Property="Background"
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 TargetName="Bd"
Property="Background"
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>
<Style TargetType="{x:Type TreeView}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TreeView}">
<Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<DockPanel>
<GridViewHeaderRowPresenter Columns="{StaticResource gvcc}"
DockPanel.Dock="Top"/>
<ItemsPresenter/>
</DockPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<TreeView Name="_list" Margin="0" BorderThickness="0" VerticalAlignment="Stretch" Background="Transparent" ItemsSource="{Binding Children}" >
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<Border CornerRadius="0" Margin="1" x:Name="back" MinWidth="70"
Background="Transparent" DataContext="{Binding}" >
<StackPanel Orientation="Horizontal" Margin="2">
<TextBlock Text="{Binding Text}" Margin="2 0"/>
</StackPanel>
</Border>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
</Window>
Window.Resources裡面是所用到的style和資源,其實整個窗口裡面就只有一個TreeView在codebehind綁定一個對象的Children屬性。這裡的treeview對象的綁定涉及到HierarchicalDataTemplate它是一個遞歸的數據模板。而在resources裡面值得注意的是幾個地方
1. <local:LevelToMarginConverter x:Key="LevelToIndentConverter"/> 這句是在定義一個Converter資源,因為我是使用綁定對象的level值來綁定前台的縮進大小,但是level屬性是int型的 要轉換為margin的thickness類型,所以我會用到一個繼承自IValueConverter的 LevelToMarginConverter 的類
[csharp]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace TreeGrid
{
public class LevelToMarginConverter : IValueConverter
{
public object Convert(object o, Type type, object parameter,
CultureInfo culture)
{
return new Thickness((int)o * c_IndentSize, 0, 0, 0);
}
public object ConvertBack(object o, Type type, object parameter,
CultureInfo culture)
{
throw new NotSupportedException();
}
private const double c_IndentSize = 15.0;
}
}
2.TreeViewItem的style中GridViewRowPresenter是用來顯示自身的值,itemspresernter是用來顯示樹形下級內容的
[csharp]
<StackPanel>
<Border Name="Bd"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}">
<GridViewRowPresenter x:Name="PART_Header"
Content="{TemplateBinding Header}"
Columns="{StaticResource gvcc}" />
</Border>
<ItemsPresenter x:Name="ItemsHost" />
</StackPanel>
3.TreeView的style中 GridViewHeaderRowPresenter用來顯示表頭,ItemsPresenter用來顯示TreeViewITem的列表
[csharp]
<span> </span><ControlTemplate TargetType="{x:Type TreeView}">
<Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<DockPanel>
<GridViewHeaderRowPresenter Columns="{StaticResource gvcc}"
DockPanel.Dock="Top"/>
<ItemsPresenter/>
</DockPanel>
</Border>
</ControlTemplate>
codebehind代碼如下
[csharp]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
using System.Collections.ObjectModel;
namespace TreeGrid
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ObjForTest root = new ObjForTest("root", "root",0,"",0);
ObjForTest c1 = new ObjForTest("CEO", "Leo",45,"M",1);
ObjForTest c2 = new ObjForTest("CFO", "Tami",46,"FM",1);
ObjForTest c3 = new ObjForTest("COO", "Jack",47,"M",1);
ObjForTest cc1 = new ObjForTest("Manager", "John", 30, "M", 2);
ObjForTest cc2 = new ObjForTest("Manager", "Lee", 31, "FM", 2);
ObjForTest cc3 = new ObjForTest("Manager", "Chris", 32, "M", 2);
ObjForTest ccc1 = new ObjForTest("Worker", "Evan", 25,"FM",3);
root.Children.Add(c1);
root.Children.Add(c2);
root.Children.Add(c3);
c1.Children.Add(cc1);
c2.Children.Add(cc2);
c3.Children.Add(cc3);
cc1.Children.Add(ccc1);
this._list.ItemsSource = root.Children;
}
}
public class ObjForTest : INotifyPropertyChanged
{
public ObjForTest(string title, string name,int age,string sex,int level)
{
this._jobTitle = title;
this._sex = sex;
this._age = age;
this._name = name;
this._level = level;
}
private string _name;
private int _age;
private string _sex;
private int _level;
private string _jobTitle;
public string Sex { get { return this._sex; } set { this._sex = value; } }
public int Age { get { return this._age; } set { this._age = value; } }
public int Level
{
get
{
return this._level;
}
set
{
_level = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Level"));
}
}
public string JobTitle
{
get { return _jobTitle; }
set
{
_jobTitle = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("JobTitle"));
}
}
public string Name
{
get { return _name; }
set
{
_name = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Name"));
}
}
private ObservableCollection<ObjForTest> _children = new ObservableCollection<ObjForTest>();
public ObservableCollection<ObjForTest> Children
{
get { return _children; }
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
ObjForTest是用來綁定的對象this._list.ItemsSource = root.Children;這句是用來綁定對象的children對象