WPF TreeView实现固定表头
1、在WPF中TreeView默认不支持固定表头的我们可以修改样式实现固定表头
新建一个TreeListView类 然后继承TreeView代码如下
public class TreeListView : TreeView,IDisposable { public TreeListView() { //this.Loaded += TreeListView_Loaded; //this.SizeChanged += TreeListView_SizeChanged; //SourceUpdated += TreeListView_SourceUpdated; } private void TreeListView_SourceUpdated(object sender, System.Windows.Data.DataTransferEventArgs e) { } double fixedColumnsWidth = 0; //int resizableColumnCount = 0; List<int> resizableColumnCount=new List<int>(); GridViewHeaderRowPresenter gridViewHeaderRowPresenter; bool shub=false; private void TreeListView_SizeChanged(object sender, SizeChangedEventArgs e) { fixedColumnsWidth = 0; var treeListView = sender as TreeListView; // 将 DependencyObject 转换为具体类型 if (treeListView != null) { double wh = treeListView.ActualWidth; for (int i = 0;i < treeListView.Columns.Count; i++) { if (treeListView.Columns[i].Width > 0) { if (resizableColumnCount.Contains(i)==false) fixedColumnsWidth += treeListView.Columns[i].Width; } else { if (resizableColumnCount.Any() == false) resizableColumnCount.Add(i); } } // 计算剩余空间 double remainingWidth = wh - fixedColumnsWidth; // 如果有可调整宽度的列,将剩余空间均分给它们 if (resizableColumnCount.Any()) { double widthPerResizableColumn = remainingWidth / resizableColumnCount.Count; foreach (int i in resizableColumnCount) { if (resizableColumnCount.Contains(i)) { treeListView.Columns[i] .Width= widthPerResizableColumn; } } } } } private void TreeListView_Loaded(object sender, RoutedEventArgs e) { gridViewHeaderRowPresenter= this.Template.FindName("HeaderPresenter", this) as GridViewHeaderRowPresenter; if(gridViewHeaderRowPresenter != null ) { gridViewHeaderRowPresenter.PreviewMouseLeftButtonDown += GridViewHeaderRowPresenter_PreviewMouseLeftButtonDown; gridViewHeaderRowPresenter.PreviewMouseMove += GridViewHeaderRowPresenter_PreviewMouseMove; gridViewHeaderRowPresenter.PreviewMouseLeftButtonUp += GridViewHeaderRowPresenter_PreviewMouseLeftButtonUp; } } private void GridViewHeaderRowPresenter_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e) { shub=false; } private void GridViewHeaderRowPresenter_PreviewMouseMove(object sender, MouseEventArgs e) { if (shub) { //var d= e.GetPosition(gridViewHeaderRowPresenter); // // 找到当前被点击的列 // var hitTestResult = VisualTreeHelper.HitTest(gridViewHeaderRowPresenter, d); // if (hitTestResult != null) // { // // 获取被点击的列标题 // var columnHeader = hitTestResult.VisualHit as GridViewColumn; // if (columnHeader != null) // { // var gridView = (GridView)gridViewHeaderRowPresenter.DataContext; // var columns = gridView.Columns; // // 检查是否是最后一列 // //if (columnHeader.c == columns.Last()) // //{ // // // 禁用拖动行为 // // e.Handled = true; // //} // } // } } } private void GridViewHeaderRowPresenter_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { shub = true; } protected override System.Windows.DependencyObject GetContainerForItemOverride() { return new TreeListViewItem(); } protected override bool IsItemItsOwnContainerOverride(object item) { return item is TreeListViewItem; } /// <summary> /// Gets or sets the collection of System.Windows.Controls.GridViewColumn /// objects that is defined for this TreeListView. /// </summary> public GridViewColumnCollection Columns { get { return (GridViewColumnCollection)GetValue(ColumnsProperty); } set { SetValue(ColumnsProperty, value); } } // Using a DependencyProperty as the backing store for Columns. This enables animation, styling, binding, etc... public static readonly System.Windows.DependencyProperty ColumnsProperty = System.Windows.DependencyProperty.Register("Columns", typeof(GridViewColumnCollection), typeof(TreeListView), new PropertyMetadata(OnColumnsChanged)); // //public Command PreviewMouseLeftButtonDownl //{ // get { return (Command)GetValue(PreviewMouseLeftButtonDownlProperty); } // set { SetValue(PreviewMouseLeftButtonDownlProperty, value); } //} //// Using a DependencyProperty as the backing store for PreviewMouseLeftButtonDownl. This enables animation, styling, binding, etc... //public static readonly DependencyProperty PreviewMouseLeftButtonDownlProperty = // DependencyProperty.Register("PreviewMouseLeftButtonDownl", typeof(Command), typeof(GridViewColumnCollection), new PropertyMetadata(PreviewMouseLeftButtonDownlcleck)); //private static void PreviewMouseLeftButtonDownlcleck(DependencyObject d, DependencyPropertyChangedEventArgs e) //{ //} private static void OnColumnsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { // 在此处理属性更改的逻辑 var treeListView = d as GridViewHeaderRowPresenter; // 将 DependencyObject 转换为具体类型 if (treeListView != null) { foreach (var columnHeader in treeListView.Columns) { // (columnHeader.).PreviewMouseLeftButtonUp += ColumnHeader_PreviewMouseLeftButtonDown; //columnHeader.PreviewMouseMove += ColumnHeader_PreviewMouseMove; //columnHeader.PreviewMouseLeftButtonUp += ColumnHeader_PreviewMouseLeftButtonUp; } // 在此处添加您的处理逻辑,例如通知其他控件 } } public void Dispose() { } } public class TreeListViewItem : TreeViewItem { /// <summary> /// Item's hierarchy in the tree /// </summary> public int Level { get { if (_level == -1) { TreeListViewItem parent = ItemsControl.ItemsControlFromItemContainer(this) as TreeListViewItem; _level = (parent != null) ? parent.Level + 1 : 0; } return _level; } } protected override DependencyObject GetContainerForItemOverride() { return new TreeListViewItem(); } protected override bool IsItemItsOwnContainerOverride(object item) { return item is TreeListViewItem; } private int _level = -1; }
样式如下:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:convert="clr-namespace:TreeListViewSample.Converters" xmlns:local="clr-namespace:TreeListViewSample.Controls"> <!--Converter for Indentation of items--> <convert:LevelToIndentConverter x:Key="LevelIndentConverter" /> <Style x:Key="ExpandCollapseToggleStyle" TargetType="{x:Type ToggleButton}"> <Setter Property="Width" Value="19"/> <Setter Property="Height" Value="13"/> <Setter Property="Focusable" Value="False"/> <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 Path=Level, Converter={StaticResource LevelIndentConverter}, RelativeSource={RelativeSource AncestorType={x:Type local:TreeListViewItem}}}" IsChecked="{Binding Path=IsExpanded, RelativeSource={RelativeSource AncestorType={x:Type local:TreeListViewItem}}}" ClickMode="Press"/> <!--首列绑定值--> <TextBlock Text="{Binding Name}"/> </DockPanel> <DataTemplate.Triggers> <DataTrigger Binding="{Binding Path=HasItems, RelativeSource={RelativeSource AncestorType={x:Type local:TreeListViewItem}}}" Value="False"> <Setter TargetName="Expander" Property="Visibility" Value="Hidden"/> </DataTrigger> </DataTemplate.Triggers> </DataTemplate> <!--<ImageBrush x:Key="gvHeaderBackground" ImageSource="/TreeListViewSample;component/Images/list_header_bk.png" />--> <Style x:Key="gvColumnHeaderStyle" TargetType="{x:Type GridViewColumnHeader}"> <Style.Setters> <Setter Property="Height" Value="34" /> <Setter Property="Background"> <Setter.Value> <LinearGradientBrush StartPoint="0,0" EndPoint="1,1"> <GradientStop Color="LightBlue" Offset="0" /> <GradientStop Color="Gray" Offset="1" /> </LinearGradientBrush> </Setter.Value> </Setter> </Style.Setters> </Style> <!--<GridViewColumnCollection x:Key="gvColumns"> <GridViewColumn Header="姓名" CellTemplate="{StaticResource CellTemplate_Name}" Width="150" /> <GridViewColumn Header="年龄" DisplayMemberBinding="{Binding Age}" Width="80" /> <GridViewColumn Header="性别" DisplayMemberBinding="{Binding Sex}" Width="80" /> <GridViewColumn Header="职务" DisplayMemberBinding="{Binding Duty}" Width="100" /> </GridViewColumnCollection>--> <!--交替行样式--> <AlternationConverter x:Key="conBack"> <SolidColorBrush>YellowGreen</SolidColorBrush> <SolidColorBrush>SkyBlue</SolidColorBrush> </AlternationConverter> <Style TargetType="{x:Type local:TreeListViewItem}"> <Setter Property="FontSize" Value="13" /> <Setter Property="Background" Value="Transparent" /> <Setter Property="SnapsToDevicePixels" Value="True" /> <Setter Property="HorizontalContentAlignment" Value="Stretch" /> <Setter Property="VerticalContentAlignment" Value="Center" /> <Setter Property="IsSelected" Value="{Binding IsSelected}" /> <Setter Property="IsExpanded" Value="{Binding IsExpanded}" /> <Setter Property="Background" Value="{Binding RelativeSource={RelativeSource Self}, Path=(local:TreeListView.AlternationIndex), Converter={StaticResource conBack}}" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:TreeListViewItem}"> <StackPanel> <Border x:Name="outerBorder" BorderThickness="0,0,0,1" BorderBrush="#FFFFFFFD" Margin="0" Padding="0" Height="30" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="True"> <Border x:Name="innerBorder" BorderThickness="0,0,0,1" BorderBrush="#FFC6C6C6" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="True"> <GridViewRowPresenter x:Name="PART_Header" Content="{TemplateBinding Header}" Columns="{Binding Columns,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=local:TreeListView}}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/> </Border> </Border> <ItemsPresenter x:Name="ItemsHost" /> </StackPanel> <ControlTemplate.Triggers> <!--<Trigger Property="local:TreeListView.AlternationIndex" Value="1"> Columns="{Binding Columns, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:TreeListView}" <Setter Property="Background" Value="#FFE1E4F7"></Setter> </Trigger>--> <Trigger Property="IsMouseOver" Value="true" SourceName="innerBorder"> <Setter Property="Foreground" Value="White"/> <Setter Property="Background" Value="#FFC66152" TargetName="innerBorder"/> </Trigger> <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}}"/>--> <Setter Property="Foreground" Value="White"/> <Setter Property="Background" Value="#FFC66152" TargetName="innerBorder"/> </Trigger> <Trigger Property="IsExpanded" Value="false"> <Setter TargetName="ItemsHost" Property="Visibility" Value="Collapsed"/> </Trigger> <Trigger Property="IsEnabled" Value="false"> <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/> </Trigger> <MultiTrigger> <MultiTrigger.Conditions> <Condition Property="IsSelected" Value="true"/> <Condition Property="IsSelectionActive" Value="false"/> </MultiTrigger.Conditions> <Setter TargetName="innerBorder" Property="Background" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/> <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/> </MultiTrigger> <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> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> <Style TargetType="{x:Type local:TreeListView}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:TreeListView}"> <!--Create a standard border around the 'TreeListView'.--> <Border Padding="{TemplateBinding Padding}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}"> <!--ScrollViewer providing horizontal scrolling functionality for both, content and headers.--> <!--<ScrollViewer HorizontalScrollBarVisibility="Auto" CanContentScroll="{TemplateBinding ScrollViewer.CanContentScroll}" VerticalScrollBarVisibility="Auto" Focusable="False">--> <DockPanel> <!--The header row. PreviewMouseLeftButtonDown="{Binding }" PreviewMouseMove="" PreviewMouseLeftButtonUp=""--> <GridViewHeaderRowPresenter DockPanel.Dock="Top" SnapsToDevicePixels="True" x:Name="HeaderPresenter" Columns="{Binding Columns,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=local:TreeListView}}" ColumnHeaderContainerStyle="{StaticResource gvColumnHeaderStyle}" > </GridViewHeaderRowPresenter> <!--ScrollViewer providing vertical scrolling Columns="{StaticResource gvColumns}" functionality for the content.--> <ScrollViewer HorizontalScrollBarVisibility="Auto" CanContentScroll="{TemplateBinding ScrollViewer.CanContentScroll}" VerticalScrollBarVisibility="Auto" Focusable="False"> <!--ItemsPresenter containg the content.--> <ItemsPresenter /> </ScrollViewer> </DockPanel> <!--</ScrollViewer>--> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> <!--<Style TargetType="GridViewHeaderRowPresenter"> <Setter Property="SnapsToDevicePixels" Value="True"/> <Setter Property="Background" Value="LightBlue"/> --><!-- 修改背景颜色 --><!-- <Setter Property="Foreground" Value="Black"/> --><!-- 修改字体颜色 --><!-- <Setter Property="FontSize" Value="14"/> --><!-- 修改字体大小 --><!-- <Setter Property="FontWeight" Value="Bold"/> --><!-- 修改字体粗细 --><!-- <Setter Property="HorizontalContentAlignment" Value="Left"/> <Setter Property="VerticalContentAlignment" Value="Center"/> <Setter Property="Height" Value="30"/> --><!-- 修改行高 --><!-- <Setter Property="Padding" Value="8"/> --><!-- 修改内边距 --><!-- <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:TypeGridViewHeaderRowPresenter}"> <GridViewHeaderRowPresenterColumns="{TemplateBindingGridView.ColumnHeaderContainerStyle}" Columns="{TemplateBindingGridView.Columns}" ColumnHeaderContextMenu="{TemplateBindingGridView.ColumnHeaderContextMenu}" ColumnHeaderToolTip="{TemplateBindingGridView.ColumnHeaderToolTip}" KeyboardNavigation.TabNavigation="Local" SnapsToDevicePixels="{TemplateBindingSnapsToDevicePixels}"/> </ControlTemplate> </Setter.Value> </Setter> </Style>--> </ResourceDictionary>
添加测试数据的代码如下
namespace TreeListViewSample.ViewModels { public class MainViewModel : ViewModelBase { private ObservableCollection<Staff> _StaffList = new ObservableCollection<Staff>(); public ObservableCollection<Staff> StaffList { get { return _StaffList; } set { _StaffList = value; this.RaisePropertyChanged("StaffList"); } } public MainViewModel() { for (int i = 0; i < 10000; i++) { Staff staff = new Staff() { Name = "张三", Age = 30, Sex = "男", Duty = "经理", IsExpanded = true }; Staff staff2 = new Staff() { Name = "张三1", Age = 21, Sex = "男", Duty = "员工", IsExpanded = true }; Staff staff3 = new Staff() { Name = "张三11", Age = 21, Sex = "男", Duty = "员工" }; staff2.StaffList.Add(staff3); staff3 = new Staff() { Name = "张三22", Age = 21, Sex = "女", Duty = "员工" }; staff2.StaffList.Add(staff3); staff.StaffList.Add(staff2); staff2 = new Staff() { Name = "张三2", Age = 22, Sex = "女", Duty = "员工" }; staff.StaffList.Add(staff2); staff2 = new Staff() { Name = "张三3", Age = 23, Sex = "女", Duty = "员工" }; staff.StaffList.Add(staff2); StaffList.Add(staff); staff = new Staff() { Name = "李四", Age = 31, Sex = "男", Duty = "副经理" }; staff2 = new Staff() { Name = "李四1", Age = 24, Sex = "女", Duty = "员工" }; staff.StaffList.Add(staff2); staff2 = new Staff() { Name = "李四2", Age = 25, Sex = "女", Duty = "员工" }; staff.StaffList.Add(staff2); staff2 = new Staff() { Name = "李四3", Age = 26, Sex = "男", Duty = "员工" }; staff.StaffList.Add(staff2); StaffList.Add(staff); staff = new Staff() { Name = "王五", Age = 32, Sex = "女", Duty = "组长" }; staff2 = new Staff() { Name = "王五1", Age = 27, Sex = "女", Duty = "员工" }; staff.StaffList.Add(staff2); staff2 = new Staff() { Name = "王五2", Age = 28, Sex = "女", Duty = "员工" }; staff.StaffList.Add(staff2); StaffList.Add(staff); } } public void ChangeNode1Value() { foreach (Staff staff in this.StaffList) { staff.Age += 1; staff.Sex = staff.Sex == "男" ? "女" : "男"; } } public void ChangeNode2Value() { foreach (Staff staff in this.StaffList) { foreach (Staff staff2 in staff.StaffList) { staff2.Age += 1; staff2.Sex = staff2.Sex == "男" ? "女" : "男"; } } } } }
最后 这里我们开启了虚拟化添加再多数据界面也不会卡顿
<local:TreeListView x:Name="treeList" ScrollViewer.CanContentScroll="True" VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling" Background="#FFE9E9E9" BorderBrush="#FF96DFFF" AlternationCount="2" ItemsSource="{Binding StaffList}"> <local:TreeListView.ItemsPanel> <ItemsPanelTemplate> <VirtualizingStackPanel x:Name="ItemsHost" IsItemsHost="True" SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" Height="Auto"/> </ItemsPanelTemplate> </local:TreeListView.ItemsPanel> <local:TreeListView.Columns> <GridViewColumnCollection> <GridViewColumn Header="姓名" CellTemplate="{StaticResource CellTemplate_Name}" Width="200" > </GridViewColumn> <GridViewColumn Header="年龄" DisplayMemberBinding="{Binding Age}" Width="80" /> <GridViewColumn Header="性别" DisplayMemberBinding="{Binding Sex}" Width="80" /> <GridViewColumn Header="职务" DisplayMemberBinding="{Binding Duty}" Width="150"/> </GridViewColumnCollection> </local:TreeListView.Columns> <local:TreeListView.ItemTemplate> <HierarchicalDataTemplate ItemsSource="{Binding StaffList}" /> </local:TreeListView.ItemTemplate> </local:TreeListView>
最后效果
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现