(关注博主后,在“粉丝专栏”,可免费阅读此文)? ? ? ??
wpf的功能非常强大,很多控件都是原生的,但是要使用TreeView+DataGrid的组合,就需要我们自己去封装实现。
我们需要的效果如图所示:
这2个图都是第三方控件自带的,并且都是收费使用。
现在我们就用原生的控件进行封装一个。
本文源码效果如下,(搞了好几天,的确有难度,所以源码也收费,便宜,赚点辛苦费)
功能如图所示, 目前已经实现了一部分。
首先说明一下,实现上面的效果,有3种方法
第一种:技术的选择是TreeView(也就是本文的演示)。
第二种:技术的选择是DataGrid。
第三种:技术的选择是ListView。
本文演示的是使用TreeView的实现。
1.首先建立一个wpf程序
2. 封装TreeGrid
namespace TreeView.TreeDataGrid.Controls
{
//这里有一个骚操作,就是把引用放在里面
using System;
using System.Collections.Specialized;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
public class TreeGrid : TreeView
{
static TreeGrid()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(TreeGrid), new FrameworkPropertyMetadata(typeof(TreeGrid)));
}
#region ColumnMappings DependencyProperty
public string ColumnMappings
{
get { return (string)GetValue(ColumnMappingsProperty); }
set { SetValue(ColumnMappingsProperty, value); }
}
public static readonly DependencyProperty ColumnMappingsProperty =
DependencyProperty.Register("ColumnMappings", typeof(string), typeof(TreeGrid),
new PropertyMetadata("", new PropertyChangedCallback(TreeGrid.OnColumnMappingsPropertyChanged)));
private static void OnColumnMappingsPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if (obj is TreeGrid)
{
(obj as TreeGrid).OnColumnMappingsValueChanged();
}
}
protected void OnColumnMappingsValueChanged()
{
if (!string.IsNullOrEmpty(ColumnMappings))
{
ResetMappingColumns(ColumnMappings);
}
}
private void ResetMappingColumns(string mapping)
{
GridViewColumnCollection items = new GridViewColumnCollection();
var columns = mapping.Split(new char[] { ';', '|' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var c in columns)
{
var index = c.IndexOf(':');
var title = "";
var name = "";
if (index > 0)
{
title = c.Substring(0, index);
name = c.Substring(index + 1);
}
else
{
title = c;
name = c;
}
DataTemplate temp = null;
var res = this.FindTreeResource<DataTemplate>(name);
if (res != null && res is DataTemplate template)
{
temp = template;
}
else
{
temp = new DataTemplate();
FrameworkElementFactory element = null;
if (items.Count == 0)
{
element = new FrameworkElementFactory(typeof(TreeItemContentControl));
element.SetValue(ContentControl.ContentProperty, new Binding(name));
}
else
{
element = new FrameworkElementFactory(typeof(TreeGridCell));
element.SetValue(ContentControl.ContentProperty, new Binding(name));
}
temp.VisualTree = element;
}
var col = new GridViewColumn
{
Width = 200,
Header = title,
CellTemplate = temp,
};
items.Add(col);
}
Columns = items;
}
#endregion
#region Columns DependencyProperty
public GridViewColumnCollection Columns
{
get { return (GridViewColumnCollection)GetValue(ColumnsProperty); }
set { SetValue(ColumnsProperty, value); }
}
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register("Columns", typeof(GridViewColumnCollection), typeof(TreeGrid),
new PropertyMetadata(null, new PropertyChangedCallback(TreeGrid.OnColumnsPropertyChanged)));
private static void OnColumnsPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if (obj is TreeGrid)
{
(obj as TreeGrid).OnColumnsValueChanged();
}
}
protected void OnColumnsValueChanged()
{
}
#endregion
#region RowHeight DependencyProperty
public double RowHeight
{
get { return (double)GetValue(RowHeightProperty); }
set { SetValue(RowHeightProperty, value); }
}
public static readonly DependencyProperty RowHeightProperty =
DependencyProperty.Register("RowHeight", typeof(double), typeof(TreeGrid),
new PropertyMetadata(30.0, new PropertyChangedCallback(TreeGrid.OnRowHeightPropertyChanged)));
private static void OnRowHeightPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if (obj is TreeGrid)
{
(obj as TreeGrid).OnRowHeightValueChanged();
}
}
protected void OnRowHeightValueChanged()
{
}
#endregion
#region ShowCellBorder DependencyProperty
public bool ShowCellBorder
{
get { return (bool)GetValue(ShowCellBorderProperty); }
set { SetValue(ShowCellBorderProperty, value); }
}
public static readonly DependencyProperty ShowCellBorderProperty =
DependencyProperty.Register("ShowCellBorder", typeof(bool), typeof(TreeGrid),
new PropertyMetadata(false, new PropertyChangedCallback(TreeGrid.OnShowCellBorderPropertyChanged)));
private static void OnShowCellBorderPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if (obj is TreeGrid)
{
(obj as TreeGrid).OnShowCellBorderValueChanged();
}
}
protected void OnShowCellBorderValueChanged()
{
}
#endregion
#region IconStroke DependencyProperty
public Brush IconStroke
{
get { return (Brush)GetValue(IconStrokeProperty); }
set { SetValue(IconStrokeProperty, value); }
}
public static readonly DependencyProperty IconStrokeProperty =
DependencyProperty.Register("IconStroke", typeof(Brush), typeof(TreeGrid),
new PropertyMetadata(new SolidColorBrush(Colors.LightGray), new PropertyChangedCallback(TreeGrid.OnIconStrokePropertyChanged)));
private static void OnIconStrokePropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if (obj is TreeGrid)
{
(obj as TreeGrid).OnIconStrokeValueChanged();
}
}
protected void OnIconStrokeValueChanged()
{
}
#endregion
#region CellBorderBrush DependencyProperty
public Brush CellBorderBrush
{
get { return (Brush)GetValue(CellBorderBrushProperty); }
set { SetValue(CellBorderBrushProperty, value); }
}
public static readonly DependencyProperty CellBorderBrushProperty =
DependencyProperty.Register("CellBorderBrush", typeof(Brush), typeof(TreeGrid),
new PropertyMetadata(new SolidColorBrush(Colors.LightGray), new PropertyChangedCallback(TreeGrid.OnCellBorderBrushPropertyChanged)));
private static void OnCellBorderBrushPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if (obj is TreeGrid)
{
(obj as TreeGrid).OnCellBorderBrushValueChanged();
}
}
protected void OnCellBorderBrushValueChanged()
{
}
#endregion
protected override DependencyObject GetContainerForItemOverride()
{
return new TreeGridItem();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is TreeGridItem;
}
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
{
base.OnItemsChanged(e);
}
}
public class TreeGridItem : TreeViewItem
{
public event EventHandler IconStateChanged;
static TreeGridItem()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(TreeGridItem), new FrameworkPropertyMetadata(typeof(TreeGridItem)));
}
public TreeGridItem()
{
this.DataContextChanged += TreeGridItem_DataContextChanged;
}
private void TreeGridItem_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (DataContext != null && DataContext is TreeItemData treeData)
{
this.SetBinding(IsExpandedProperty, new Binding("IsExpanded") { Source = treeData, Mode = BindingMode.TwoWay });
}
}
protected override void OnVisualParentChanged(DependencyObject oldParent)
{
base.OnVisualParentChanged(oldParent);
}
protected override DependencyObject GetContainerForItemOverride()
{
return new TreeGridItem();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is TreeGridItem;
}
}
/*
* https://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Controls/GridViewRowPresenter.cs,ace7d38fc902993d
* GridViewRow里的每个元素,增加了一个默认的Margin,这样在设置边框的时候会比较麻烦,在运行时去掉
*/
public class TreeGridCell : ContentControl
{
static TreeGridCell()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(TreeGridCell), new FrameworkPropertyMetadata(typeof(TreeGridCell)));
}
public TreeGridCell()
{
Loaded += TreeGridCell_Loaded;
}
private void TreeGridCell_Loaded(object sender, RoutedEventArgs e)
{
Loaded -= TreeGridCell_Loaded;
var p = VisualTreeHelper.GetParent(this);
if (p != null && p is FrameworkElement f && f.Margin.Left > 0)
{
f.Margin = new Thickness(0);
}
}
}
public static class TreeHelper
{
public static T FindParent<T>(this DependencyObject obj)
{
var p = VisualTreeHelper.GetParent(obj);
if (p == null)
{
return default(T);
}
if (p is T tt)
{
return tt;
}
return FindParent<T>(p);
}
public static T FindTreeResource<T>(this FrameworkElement obj, string key)
{
if (obj == null)
{
return default(T);
}
var r = obj.TryFindResource(key);
if (r == null)
{
r = Application.Current.TryFindResource(key);
}
if (r != null && r is T t)
{
return t;
}
var p = FindParent<FrameworkElement>(obj);
if (p != null)
{
return FindTreeResource<T>(p, key);
}
return default(T);
}
}
}
3.TreeGrid.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TreeView.TreeDataGrid.Controls"
>
<SolidColorBrush x:Key="TreeIconStroke" Color="GreenYellow" />
<Style x:Key="TreeGridItemStyle" TargetType="{x:Type local:TreeGridItem}">
<Setter Property="Foreground" Value="Black"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="IsExpanded" Value="True"/>
<Setter Property="BorderBrush" Value="Wheat"/>
<Setter Property="BorderThickness" Value="0,0,0,1"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:TreeGridItem}">
<StackPanel>
<Border Name="Bd"
Background="Transparent"
BorderBrush="{TemplateBinding BorderBrush}"
Padding="{TemplateBinding Padding}">
<GridViewRowPresenter x:Name="PART_Header"
Content="{TemplateBinding Header}"
Columns="{Binding Path=Columns,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=local:TreeGrid}}" />
</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>
<MultiTrigger>
<!--移动变色-->
<MultiTrigger.Conditions>
<Condition Property="IsFocused" Value="False"/>
<Condition SourceName="Bd" Property="IsMouseOver" Value="true"/>
</MultiTrigger.Conditions>
<Setter Property="Background" Value=" red" TargetName="Bd"/>
</MultiTrigger>
<Trigger Property="IsSelected" Value="true">
<!--选中的背景颜色-->
<Setter TargetName="Bd" Property="Background" Value="YellowGreen"/>
<!--<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>-->
<Setter Property="Foreground" Value="Red"/>
</Trigger>
<!--隔行换色-->
<!--<Trigger Property="AlternationIndex" Value="0" >
<Setter TargetName="Bd" Property="Background" Value="blue" />
</Trigger>
<Trigger Property="AlternationIndex" Value="2" >
<Setter TargetName="Bd" Property="Background" Value="black" />
</Trigger>-->
<!--<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=local:TreeGrid}, Path=Columns.Count }" Value="0">
<Setter TargetName="Bd" Property="Background" Value="#FFD3D3D3"/>
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=local:TreeGrid}, Path=Columns.Count}" Value="2">
<Setter TargetName="Bd" Property="Background" Value="#FFE6E6E6"/>
</DataTrigger>-->
<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.Triggers>
<!--隔行换色-->
<Trigger Property="AlternationIndex" Value="0" >
<Setter Property="Background" Value="#e7e7e7" />
</Trigger>
<Trigger Property="AlternationIndex" Value="1" >
<Setter Property="Background" Value="#f2f2f2" />
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="{x:Type local:TreeGridItem}" BasedOn="{StaticResource TreeGridItemStyle}"/>
<Style TargetType="{x:Type local:TreeGridCell}">
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="BorderBrush" Value="Red"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:TreeGridCell}">
<Border x:Name="CellBorder"
Margin="0,0,-0.5,0"
Background="{TemplateBinding Background}"
BorderBrush="Red"
BorderThickness="0,0,0,1">
<ContentControl Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
SnapsToDevicePixels="True"/>
</Border>
<!--BorderBrush="Red"下划线颜色-->
<!--<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=local:TreeGrid},Path=ShowCellBorder}" Value="true">
<Setter TargetName="CellBorder" Property="BorderBrush" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=local:TreeGrid},Path=CellBorderBrush}" />
</DataTrigger>
</ControlTemplate.Triggers>-->
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type local:TreeGrid}">
<Setter Property="IconStroke" Value="{StaticResource TreeIconStroke}"/>
<Setter Property="ItemContainerStyle" Value="{StaticResource {x:Type local:TreeGridItem}}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:TreeGrid}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="0">
<!--最大边框-->
<DockPanel>
<!--标题栏-->
<GridViewHeaderRowPresenter IsHitTestVisible="False" Columns="{TemplateBinding Columns}" Height="{TemplateBinding RowHeight}" DockPanel.Dock="Top" >
</GridViewHeaderRowPresenter>
<ItemsPresenter />
</DockPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
4.代码很多,最终的源码格式
需要源码请联系我。
本文来源: