• WPF实现树形表格控件(TreeListView)


    前言

      本文将探讨如何利用WPF框架实现树形表格控件,该控件不仅能够有效地展示复杂的层级数据,还能够提供丰富的个性化定制选项。我们将介绍如何使用WPF提供的控件、模板、布局、数据绑定等技术来构建这样一个树形表格。

    一、运行效果

    1.1默认样式

    1.2 自定义样式

    二、代码实现

    2.1 创建自定义控件(TreeListView)

          新建一个继承自TreeView的控件,并定义一个类型为ViewBase的View依赖属性,用于在代码中指定列。

    复制代码
    public class TreeListView : TreeView
    {
            public ViewBase View
            {
                get { return (ViewBase)GetValue(ViewProperty); }
                set { SetValue(ViewProperty, value); }
            }
    
            public static readonly DependencyProperty ViewProperty =
                DependencyProperty.Register("View", typeof(ViewBase), typeof(TreeListView));
    }
    复制代码

    2.2 在TreeListView控件模板中处理列头

       为了在TreeListView中显示列头,需要在合适的位置添加GridViewHeaderRowPresenter控件,并在Columns属性上绑定我们之前定义的View.Columns属性。下面我们首先来分析TreeView控件模板的代码。

    复制代码
    <ControlTemplate TargetType="{x:Type TreeView}">
        <Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" SnapsToDevicePixels="true">
            <ScrollViewer x:Name="_tv_scrollviewer_" Background="{TemplateBinding Background}" CanContentScroll="false" Focusable="false" HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}">
                <ItemsPresenter/>
            ScrollViewer>
        Border>
        <ControlTemplate.Triggers>
            <Trigger Property="IsEnabled" Value="false">
                <Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
            Trigger>
            <Trigger Property="VirtualizingPanel.IsVirtualizing" Value="true">
                <Setter Property="CanContentScroll" TargetName="_tv_scrollviewer_" Value="true"/>
            Trigger>
        ControlTemplate.Triggers>
    ControlTemplate>
    复制代码

          通过以上代码我们可以看出,只要将GridViewHeaderRowPresenter控件添加到ScrollViewer控件上面即可实现列头功能,但这样会有一个问题,那就是内容宽度超出控件宽度后,鼠标拖动横向滚动条时列头不会跟随下方的数据列表一起滚动。为解决这个问题我们需要将GridViewHeaderRowPresenter放置到ScrollViewer控件模板中,以下为完整代码。

    复制代码
    <Style x:Key="{x:Static GridView.GridViewScrollViewerStyleKey}" TargetType="{x:Type ScrollViewer}">
        <Setter Property="Focusable" Value="false" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type ScrollViewer}">
                    <Grid Background="{TemplateBinding Background}" SnapsToDevicePixels="true">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="*" />
                            <ColumnDefinition Width="Auto" />
                        Grid.ColumnDefinitions>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="*" />
                            <RowDefinition Height="Auto" />
                        Grid.RowDefinitions>
    
                        <DockPanel Margin="{TemplateBinding Padding}">
                            <ScrollViewer
                                    DockPanel.Dock="Top"
                                    Focusable="false"
                                    HorizontalScrollBarVisibility="Hidden"
                                    VerticalScrollBarVisibility="Hidden">
                                <GridViewHeaderRowPresenter
                                        Margin="2,0,2,0"
                                        AllowsColumnReorder="{Binding TemplatedParent.View.AllowsColumnReorder, RelativeSource={RelativeSource TemplatedParent}}"
                                        ColumnHeaderContainerStyle="{Binding TemplatedParent.View.ColumnHeaderContainerStyle, RelativeSource={RelativeSource TemplatedParent}}"
                                        ColumnHeaderContextMenu="{Binding TemplatedParent.View.ColumnHeaderContextMenu, RelativeSource={RelativeSource TemplatedParent}}"
                                        ColumnHeaderStringFormat="{Binding TemplatedParent.View.ColumnHeaderStringFormat, RelativeSource={RelativeSource TemplatedParent}}"
                                        ColumnHeaderTemplate="{Binding TemplatedParent.View.ColumnHeaderTemplate, RelativeSource={RelativeSource TemplatedParent}}"
                                        ColumnHeaderTemplateSelector="{Binding TemplatedParent.View.ColumnHeaderTemplateSelector, RelativeSource={RelativeSource TemplatedParent}}"
                                        ColumnHeaderToolTip="{Binding TemplatedParent.View.ColumnHeaderToolTip, RelativeSource={RelativeSource TemplatedParent}}"
                                        Columns="{Binding TemplatedParent.View.Columns, RelativeSource={RelativeSource TemplatedParent}}"
                                        SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
                            ScrollViewer>
                            <ScrollContentPresenter
                                    x:Name="PART_ScrollContentPresenter"
                                    CanContentScroll="{TemplateBinding CanContentScroll}"
                                    Content="{TemplateBinding Content}"
                                    ContentTemplate="{TemplateBinding ContentTemplate}"
                                    KeyboardNavigation.DirectionalNavigation="Local"
                                    SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
                        DockPanel>
    
                        <ScrollBar
                                x:Name="PART_HorizontalScrollBar"
                                Grid.Row="1"
                                Cursor="Arrow"
                                Maximum="{TemplateBinding ScrollableWidth}"
                                Minimum="0.0"
                                Orientation="Horizontal"
                                ViewportSize="{TemplateBinding ViewportWidth}"
                                Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"
                                Value="{Binding HorizontalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" />
                        <ScrollBar
                                x:Name="PART_VerticalScrollBar"
                                Grid.Column="1"
                                Cursor="Arrow"
                                Maximum="{TemplateBinding ScrollableHeight}"
                                Minimum="0.0"
                                Orientation="Vertical"
                                ViewportSize="{TemplateBinding ViewportHeight}"
                                Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"
                                Value="{Binding VerticalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" />
                        <DockPanel
                                Grid.Row="1"
                                Grid.Column="1"
                                Background="{Binding Background, ElementName=PART_VerticalScrollBar}"
                                LastChildFill="false">
                            <Rectangle
                                    Width="1"
                                    DockPanel.Dock="Left"
                                    Fill="White"
                                    Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}" />
                            <Rectangle
                                    Height="1"
                                    DockPanel.Dock="Top"
                                    Fill="White"
                                    Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}" />
                        DockPanel>
                    Grid>
                ControlTemplate>
            Setter.Value>
        Setter>
    Style>
    
    <Style TargetType="{x:Type local:TreeListView}">
        <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto" />
        <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" />
        <Setter Property="ScrollViewer.CanContentScroll" Value="true" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:TreeListView}">
                    <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
                        <ScrollViewer Padding="{TemplateBinding Padding}" Style="{StaticResource {x:Static GridView.GridViewScrollViewerStyleKey}}">
                            <ItemsPresenter />
                        ScrollViewer>
                    Border>
                ControlTemplate>
            Setter.Value>
        Setter>
    Style>
    复制代码

    2.3 在TreeListViewItem模板中处理子项的展开和收缩

          新建一个继承自TreeViewItem的类,命名为TreeListViewItem(如有个性化需求,可以在该类中处理),编辑控件模板,在模板中添加以下代码。

    复制代码
    <Style TargetType="{x:Type local:TreeListViewItem}">
        <Setter Property="BorderThickness" Value="1" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:TreeListViewItem}">
                    <StackPanel>
                        <Border
                                Name="Bd"
                                Padding="{TemplateBinding Padding}"
                                Background="{TemplateBinding Background}"
                                BorderBrush="{TemplateBinding BorderBrush}"
                                BorderThickness="{TemplateBinding BorderThickness}">
                            <GridViewRowPresenter
                                    x:Name="PART_Header"
                                    Columns="{Binding RelativeSource={RelativeSource AncestorType=local:TreeListView}, Path=View.Columns}"
                                    Content="{TemplateBinding Header}" />
                        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="extensions:TreeViewItemExtensions.IsMouseDirectlyOverItem" Value="True" />
                            MultiTrigger.Conditions>
                            <Setter TargetName="Bd" Property="Background" Value="{StaticResource Item.MouseOver.Background}" />
                            <Setter TargetName="Bd" Property="BorderBrush" Value="{StaticResource Item.MouseOver.Border}" />
                        MultiTrigger>
                        <MultiTrigger>
                            <MultiTrigger.Conditions>
                                <Condition Property="Selector.IsSelectionActive" Value="False" />
                                <Condition Property="IsSelected" Value="True" />
                            MultiTrigger.Conditions>
                            <Setter TargetName="Bd" Property="Background" Value="{StaticResource Item.SelectedInactive.Background}" />
                            <Setter TargetName="Bd" Property="BorderBrush" Value="{StaticResource Item.SelectedInactive.Border}" />
                        MultiTrigger>
                        <MultiTrigger>
                            <MultiTrigger.Conditions>
                                <Condition Property="Selector.IsSelectionActive" Value="True" />
                                <Condition Property="IsSelected" Value="True" />
                            MultiTrigger.Conditions>
                            <Setter TargetName="Bd" Property="Background" Value="{StaticResource Item.SelectedActive.Background}" />
                            <Setter TargetName="Bd" Property="BorderBrush" Value="{StaticResource Item.SelectedActive.Border}" />
                        MultiTrigger>
                        <Trigger Property="IsEnabled" Value="False">
                            <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" />
                        Trigger>
                    ControlTemplate.Triggers>
                ControlTemplate>
            Setter.Value>
        Setter>
    Style>
    复制代码

        此处的核心在于模板中添加了GridViewRowPresenter控件,并在Columns属性上绑定了我们之前定义的View.Columns属性,这样就可以在每一行上面显示列数据。还有一个关键点是ItemsPresenter,它用于显示子项数据,此处命名为ItemsHost,它由属性触发器中的代码来控件展开和收起。以下是属性触发器代码。

    <Trigger Property="IsExpanded" Value="false">
        <Setter TargetName="ItemsHost" Property="Visibility" Value="Collapsed" />
    Trigger>

    2.4 在单元格模板中控件子项的展开与收起

       为了达到展开和收起的效果,需要在首列的单元格中控制TreeListViewItem的IsExpanded属性。以下为完整代码。

    复制代码
    <DataTemplate x:Key="ExpandCellTemplate">
        <DockPanel>
            <ToggleButton
                                x:Name="Expander"
                                Margin="{Binding Path=Level, Converter={StaticResource LevelIndentConverter}, RelativeSource={RelativeSource AncestorType={x:Type TreeListViewItem}}}"
                                ClickMode="Press"
                                IsChecked="{Binding Path=IsExpanded, RelativeSource={RelativeSource AncestorType={x:Type TreeListViewItem}}}"
                                Style="{StaticResource ExpandCollapseToggleStyle}" />
            <TextBlock Text="{Binding Property1}" />
        DockPanel>
        <DataTemplate.Triggers>
            <DataTrigger Binding="{Binding Path=HasItems, RelativeSource={RelativeSource AncestorType={x:Type TreeListViewItem}}}" Value="False">
                <Setter TargetName="Expander" Property="Visibility" Value="Hidden" />
            DataTrigger>
        DataTemplate.Triggers>
    DataTemplate>
    复制代码

    其关键代码为

    IsChecked="{Binding Path=IsExpanded, RelativeSource={RelativeSource AncestorType={x:Type TreeListViewItem}}}"

    2.5 控件使用

    复制代码
    <TreeListView ItemsSource="{Binding Collection}">
        <TreeListView.ItemTemplate>
            <HierarchicalDataTemplate ItemsSource="{Binding Collection, IsAsync=True}" />
        TreeListView.ItemTemplate>
        <TreeListView.View>
            <GridView>
                <GridViewColumn CellTemplate="{StaticResource ExpandCellTemplate}" Header="Property1" />
                <GridViewColumn DisplayMemberBinding="{Binding Property2}" Header="Property2" />
                <GridViewColumn DisplayMemberBinding="{Binding Property3}" Header="Property3" />
                <GridViewColumn DisplayMemberBinding="{Binding Property4}" Header="Property4" />
                <GridViewColumn DisplayMemberBinding="{Binding Property5}" Header="Property5" />
                <GridViewColumn DisplayMemberBinding="{Binding Property6}" Header="Property6" />
                <GridViewColumn DisplayMemberBinding="{Binding Property7}" Header="Property7" />
                <GridViewColumn DisplayMemberBinding="{Binding Property8}" Header="Property8" />
                <GridViewColumn DisplayMemberBinding="{Binding Property9}" Header="Property9" />
                <GridViewColumn DisplayMemberBinding="{Binding Property10}" Header="Property10" />
                <GridViewColumn DisplayMemberBinding="{Binding Property11}" Header="Property11" />
                <GridViewColumn DisplayMemberBinding="{Binding Property12}" Header="Property12" />
            GridView>
        TreeListView.View>
    TreeListView>
    复制代码

     

     技术交流群
     
     联系方式
  • 相关阅读:
    Spring注解大全,最后一个经常容易记不住
    kafka入门小结( windows本地环境)
    电路综合-基于简化实频的集总参数电路匹配1
    spring 5.1.x 本地构建 build.gradle文件配置
    如何导入StackOverflow数据库
    【Python】Python列表排序 list.sort方法和内置函数sorted用法
    华为1000人校园实验记录
    TI IWR1642毫米波雷达使用串口原始数据采集与分析
    k8s--基础--25.4--Helm--部署
    Windows Server 2016 AD域(三)禁止域中的计算机访问特定IP地址
  • 原文地址:https://www.cnblogs.com/qushi2020/p/18109923