本文展示了如何通过自定义样式和XAML代码实现WPFDataGrid的合计栏功能,以及标题头的搜索功能。代码示例中详细解释了合计栏的实现流程,包括ItemsControl的使用和宽度计算逻辑,同时也讨论了用户拖动标题头后的排序处理和宽度自适应。文章还提及了性能分析和适用环境,适用于需要对WPFDataGrid进行增强功能开发的开发者。 摘要由CSDN通过智能技术生成

最近准备做几期wpf datagrid优化功能的博客,包括合计栏,标题头带搜索功能,标题头带感叹号的提示框,ui优化等等.本期展示合计栏,后续再添加其他功能代码

1.效果展示

实现wpf datagrid的合计栏,标题头搜索,分页,自定义滚动条, 话不多说 先上效果
合计栏效果展示
在这里插入图片描述
标题头筛选UI还未调整,请忽略

2.合计栏实现流程及代码

(1)重写datagrid样式,在datagrid底部添加一个ItemsControl,用于展示合计的项目同时用ScrollViewer包裹ItemsControl,使之能跟随滚动条滚动
    <Style x:Key="DesignTotalDataGrid" TargetType="{x:Type controls:DataGrid}">
        <Setter Property="IsReadOnly" Value="True"/>
        <Setter Property="Background" Value="#FFFAFAFA" />
        <Setter Property="Foreground" Value="#DD000000" />
        <Setter Property="BorderBrush" Value="#1F000000" />
        <Setter Property="BorderThickness" Value="1" />
        <Setter Property="AutoGenerateColumns" Value="False" />
        <Setter Property="CanUserAddRows" Value="False" />
        <Setter Property="FontSize" Value="13" />
        <Setter Property="GridLinesVisibility" Value="Horizontal" />
        <Setter Property="HorizontalGridLinesBrush">
            <Setter.Value>
                <MultiBinding Converter="{StaticResource RemoveAlphaBrushConverter}">
                    <Binding Path="BorderBrush" RelativeSource="{RelativeSource Self}" />
                    <Binding Path="Background" RelativeSource="{RelativeSource Self}" />
                </MultiBinding>
            </Setter.Value>
        </Setter>
        <Setter Property="VerticalGridLinesBrush" Value="{Binding HorizontalGridLinesBrush, RelativeSource={RelativeSource Self}}" />
        <Setter Property="RowDetailsVisibilityMode" Value="VisibleWhenSelected" />
        <Setter Property="HeadersVisibility" Value="Column" />
        <Setter Property="ScrollViewer.CanContentScroll" Value="true" />
        <Setter Property="ScrollViewer.PanningMode" Value="Both" />
        <Setter Property="Stylus.IsFlicksEnabled" Value="False" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type controls:DataGrid}">
                    <Border
                        Padding="{TemplateBinding Padding}"
                        Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}"
                        CornerRadius="{TemplateBinding assist:DataGridAssist.CornerRadius}"
                        SnapsToDevicePixels="True"
                            <Grid.RowDefinitions>
                                <RowDefinition/>
                                <RowDefinition Height="auto"/>
                            </Grid.RowDefinitions>
                            <ScrollViewer x:Name="DG_ScrollViewer" Focusable="false">
                                <ScrollViewer.Template>
                                    <ControlTemplate TargetType="{x:Type ScrollViewer}">
                                            <Grid.ColumnDefinitions>
                                                <ColumnDefinition Width="Auto" />
                                                <ColumnDefinition Width="*" />
                                                <ColumnDefinition Width="Auto" />
                                            </Grid.ColumnDefinitions>
                                            <Grid.RowDefinitions>
                                                <RowDefinition Height="Auto" />
                                                <RowDefinition Height="*" />
                                                <RowDefinition Height="Auto" />
                                            </Grid.RowDefinitions>
                                            <Border
                                            Grid.Row="0"
                                            Grid.Column="1"
                                            Visibility="{Binding HeadersVisibility, ConverterParameter={x:Static DataGridHeadersVisibility.Column}, Converter={x:Static DataGrid.HeadersVisibilityConverter}, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}">
                                                <DataGridColumnHeadersPresenter x:Name="PART_ColumnHeadersPresenter" />
                                            </Border>
                                            <ScrollContentPresenter
                                            x:Name="PART_ScrollContentPresenter"
                                            Grid.Row="1"
                                            Grid.Column="0"
                                            Grid.ColumnSpan="2"
                                            CanContentScroll="{TemplateBinding CanContentScroll}" />
                                            <ScrollBar
                                            x:Name="PART_VerticalScrollBar"
                                            Grid.Row="1"
                                            Style="{StaticResource ScrollBarStyle}"
                                            Grid.Column="2"
                                            Maximum="{TemplateBinding ScrollableHeight}"
                                            Orientation="Vertical"
                                            ViewportSize="{TemplateBinding ViewportHeight}"
                                            Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"
                                            Value="{Binding VerticalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" />
                                            <Grid Grid.Row="3" Grid.Column="1">
                                                <Grid.ColumnDefinitions>
                                                    <ColumnDefinition Width="{Binding NonFrozenColumnsViewportHorizontalOffset, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" />
                                                    <ColumnDefinition Width="*" />
                                                </Grid.ColumnDefinitions>
                                                <ScrollBar
                                                x:Name="PART_HorizontalScrollBar"
                                                Grid.Column="1"
                                                Maximum="{TemplateBinding ScrollableWidth}"
                                                Orientation="Horizontal"
                                                Style="{StaticResource ScrollBarStyle}"
                                                ViewportSize="{TemplateBinding ViewportWidth}"
                                                Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"
                                                Value="{Binding HorizontalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" />
                                            </Grid>
                                        </Grid>
                                    </ControlTemplate>
                                </ScrollViewer.Template>
                                <ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
                            </ScrollViewer>
                            <!--合计栏添加开始-->
                            <Border BorderThickness="0 1 0 0" BorderBrush="#1F000000"  Grid.Row="1"  Effect="{StaticResource EffectShadow2}">
                                    <ScrollViewer x:Name="scrollViewer" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden">
                                        <ItemsControl x:Name="itemsControl">
                                            <ItemsControl.ItemsPanel>
                                                <ItemsPanelTemplate>
                                                    <StackPanel Orientation="Horizontal" VerticalAlignment="Center"/>
                                                </ItemsPanelTemplate>
                                            </ItemsControl.ItemsPanel>
                                            <ItemsControl.ItemTemplate>
                                                <DataTemplate>
                                                    <Border  Height="{Binding RelativeSource={RelativeSource Self}, Path=(assist:DataGridAssist.CellHeight)}"  Width="{Binding Width}">
                                                        <TextBlock Text="{Binding Content}" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="3 0"/>
                                                    </Border>
                                                </DataTemplate>
                                            </ItemsControl.ItemTemplate>
                                        </ItemsControl>
                                    </ScrollViewer>
                                </Grid>
                            </Border>
                        </Grid>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Style.Triggers>
            <MultiTrigger>
                <MultiTrigger.Conditions>
                    <Condition Property="IsGrouping" Value="true" />
                </MultiTrigger.Conditions>
                <Setter Property="ScrollViewer.CanContentScroll" Value="false" />
            </MultiTrigger>
        </Style.Triggers>
    </Style>
(2)合计栏中每一项宽度的计算

每一项宽度的计算是一个重点,因为每一项的宽度是随内容变化的,同时用户可以拖拽改变每一列的宽度,每次宽度的改变,都得重新计算,这里请注意Border的宽度 Width=“{Binding Width}”,回到cs代码 进行逻辑实现

public static readonly DependencyProperty TotalItemsProperty =
            DependencyProperty.Register("TotalItems", typeof(object), typeof(DataGrid), new PropertyMetadata(null, TotalItemsCallBack));
        public object OriginItemsSource { get; set; }
        public  object ItemsSources
            get { return (object)GetValue(ItemsSourcesProperty); }
            set { SetValue(ItemsSourcesProperty, value); }

以上代码(来自DataGrid自定义样式类)是合计栏的数据来源,是一个对象,通过后端接口返回的,当然,也可以由前端自己合计,对TotalItems进行赋值即可,赋值方式如下(来自ViewModel业务代码)
在这里插入图片描述

(3)TotalItemsCallBack(合计项赋值后回调)代码分析
        private static void TotalItemsCallBack(DependencyObject d, DependencyPropertyChangedEventArgs e)
            DataGrid dataGrid = d as DataGrid;
            if (e.NewValue != null && dataGrid.itemsControl != null)
                if (e.NewValue is List<string> totalItems)
                    //数组和对象都要转为数组;
                    //排序问题处理
                    System.Threading.Tasks.Task.Factory.StartNew(() =>
                        System.Threading.Thread.Sleep(1000);
                        Application.Current.Dispatcher.BeginInvoke((Action)(() =>
                            List<DataGridTotal> dataGrids = new List<DataGridTotal>();
                            for (int i = 0; i < dataGrid.Columns.Count; i++)
                                if (dataGrid.Columns[i].Visibility == Visibility.Visible)
                                    if (totalItems != null && totalItems.Count >= i + 1 && totalItems[i] != null)
                                        dataGrids.Add(new DataGridTotal { Width = dataGrid.Columns[i].ActualWidth, Content = totalItems[i], Index = dataGrid.Columns[i].DisplayIndex });
                                        dataGrids.Add(new DataGridTotal { Width = dataGrid.Columns[i].ActualWidth, Content = "-", Index = dataGrid.Columns[i].DisplayIndex });
                            dataGrids = dataGrids.OrderBy(x => x.Index).ToList();
                            dataGrid.itemsControl.ItemsSource = dataGrids;
                        }));
                    });
                else if(e.NewValue is object obj){
                    System.Threading.Tasks.Task.Factory.StartNew(() =>
                        System.Threading.Thread.Sleep(1000);
                        Application.Current.Dispatcher.BeginInvoke((Action)(() =>
                            List<DataGridTotal> dataGrids = new List<DataGridTotal>();
                            for (int i = 0; i < dataGrid.Columns.Count; i++)
                                if (dataGrid.Columns[i].Visibility == Visibility.Visible)
                                    var content = "-";
                                    if (obj.GetType().GetProperty(dataGrid.Columns[i].SortMemberPath) != null)
                                        content = obj.GetType().GetProperty(dataGrid.Columns[i].SortMemberPath).GetValue(obj, null)?.ToString();
                                    dataGrids.Add(new DataGridTotal { Width = dataGrid.Columns[i].ActualWidth, Content = string.IsNullOrEmpty(content) ? "-" : content, Index = dataGrid.Columns[i].DisplayIndex });
                            dataGrids = dataGrids.OrderBy(x => x.Index).ToList();
                            dataGrid.itemsControl.ItemsSource = dataGrids;
                        }));
                    });
            else {
                System.Threading.Tasks.Task.Factory.StartNew(() => {
                    System.Threading.Thread.Sleep(1000);
                    Application.Current.Dispatcher.BeginInvoke((Action)(() =>
                        List<DataGridTotal> dataGrids = new List<DataGridTotal>();
                        for (int i = 0; i < dataGrid.Columns.Count; i++)
                            if (dataGrid.Columns[i].Visibility == Visibility.Visible)
                                dataGrids.Add(new DataGridTotal { Width = dataGrid.Columns[i].ActualWidth, Content = "-", Index = dataGrid.Columns[i].DisplayIndex });
                        dataGrids = dataGrids.OrderBy(x => x.Index).ToList();
                        dataGrid.itemsControl.ItemsSource = dataGrids;
                    }));
                });

TotalItems赋值时可以是数组,也可以是对象,数组的话需要按照顺序从左到右一次返回;
Width = dataGrid.Columns[i].ActualWidth是宽度的首次计算;
Index = dataGrid.Columns[i].DisplayIndex 是排序的顺序,确保合计的项不会错位;
DataGridTotal类如下:

public class DataGridTotal
        /// <summary>
        /// 内容
        /// </summary>
        public string Content { get; set; }
        /// <summary>
        /// 宽度
        /// </summary>
        public double Width { get; set; }
        /// <summary>
        /// 排序
        /// </summary>
        public int Index { get; set; } 
        /// <summary>
        /// 标题头
        /// </summary>
        public string Header { get; set; }

加入异步是为了减少前端自己合计 数据量大时的卡顿效果

(4)用户拖动标题头,顺序发生改变后逻辑处理

对合计栏的排序进行重新计算

        private void DataGrid_ColumnDisplayIndexChanged(object sender, DataGridColumnEventArgs e)
            if (TotalItems != null)
                List<DataGridTotal> dataGrids = new List<DataGridTotal>();
                if (TotalItems is List<string> items)
                    for (int i = 0; i < Columns.Count; i++)
                        if (Columns[i].Visibility == Visibility.Visible)
                            dataGrids.Add(new DataGridTotal { Width = Columns[i].ActualWidth, Content = items[i], Index = Columns[i].DisplayIndex });
                else if(TotalItems is object obj)
                    for (int i = 0; i < Columns.Count; i++)
                        if (Columns[i].Visibility == Visibility.Visible)
                            var content = "-";
                            if (obj.GetType().GetProperty(Columns[i].SortMemberPath) != null)
                                content = obj.GetType().GetProperty(Columns[i].SortMemberPath).GetValue(obj, null)?.ToString();
                            dataGrids.Add(new DataGridTotal { Width = Columns[i].ActualWidth, Content = string.IsNullOrEmpty(content)?"-":content, Index = Columns[i].DisplayIndex });
                dataGrids = dataGrids.OrderBy(x => x.Index).ToList();
                itemsControl.ItemsSource = dataGrids;
            else {
                List<DataGridTotal> dataGrids = new List<DataGridTotal>();
                for (int i = 0; i < Columns.Count; i++)
                    if (Columns[i].Visibility == Visibility.Visible)
                        dataGrids.Add(new DataGridTotal { Width = Columns[i].ActualWidth, Content = "-", Index = Columns[i].DisplayIndex });
                dataGrids = dataGrids.OrderBy(x => x.Index).ToList();
                itemsControl.ItemsSource = dataGrids;
(5)合计栏宽度自适应

内容发生改变,列宽自动撑开后合计栏宽度的自适应,用户拖动列的宽度后,合计栏的自适应

private void OwnScrrow_ScrollChanged(object sender, ScrollChangedEventArgs e)
            Console.WriteLine("----------------------------");
            Console.WriteLine("ScrollableWidth:"+ownScrrow.ScrollableWidth);
            Console.WriteLine("ExtentWidth:"+ownScrrow.ExtentWidth);
            Console.WriteLine("VerticalOffset:" + ownScrrow.VerticalOffset);
            Console.WriteLine("HorizontalOffset:" + ownScrrow.HorizontalOffset);
            DataGrid_ColumnDisplayIndexChanged(null, null);
            if (ownScrrow.ScrollableWidth != horOffset)
                horOffset = ownScrrow.ScrollableWidth;
                //宽度发生改变 通知滚动条重新渲染
                Console.WriteLine(horOffset);
                DataGrid_ColumnDisplayIndexChanged(null, null);
            scrollViewer.ScrollToHorizontalOffset((sender as ScrollViewer).HorizontalOffset);

3.性能分析

windows xp(客户那里找来的,估计年龄比我还大) 256m内存运行流畅,高于此配置均能完美运行

gitee地址

根据行数,列数自动创建表格,使用WPF中的Grid很容易实现,并且容易实现单元格的合并拆分。主要需要解决两个问题: 单元格的定位,那一行,那一列,占几行,占几列 单元格的边框 行,列,占行,占列该问题比较容易解决,Grid分行,分列后,添加的控件可以使用Grid.Row,Grid.Column,Grid.RowSpan,Grid.ColumnSpan解决。单元格边框每一个单元格使用一个Border,
wpfDataGrid中,添加某一列的合计,并在最下方固定显示。本列子不使用ToolKit(有3.5和4.0的限制),添加一个类,重写DataGrid。可以在DataGrid上下左右绑定一个DataGrid,类似Footer属性。示例可直接运行,简单易懂。 (思路:两个DataGrid(mainDG,bottomDG),bottomDG的左右边binding 主表mainDG。并bottomDG绑定数据源(只有一行(合计的值)),),合计行的列宽可随着mainDG的改变而改变。
一、datagrid继承体系 “想要说些什么 又不知从何说起”,每当想要写一些关于wpf的文章,总是沉思良久,怕自己写不好。今天我想要说的是wpfdatagrid控件。我们先来看看它在整个类的层次结构:
WPF的Grid布局使用起来很方便,但如果我想绘制一个带有边线的表格则显得有点儿力不从心。虽然Grid有ShowGridLines这个bool类型的属性,但此属性设为true时显示的虚线而且不能改变颜色。比如下面的代码: <Window x:Class="Table.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2...
### 回答1: WPF(Windows Presentation Foundation)是一种用于创建Windows应用程序界面的框架。在WPF中,可以使用DataGrid控件来展示和编辑数据。折叠DataGrid可以让用户在需要时展开和收起数据,以便更好地组织和管理信息。 要在WPF实现折叠DataGrid,可以使用一些额外的控件和代码逻辑。首先,可以使用TreeView控件作为外层容器,用于显示可展开和收起的树形结构。然后,在TreeView的各个节点中,嵌套使用DataGrid控件来展示具体的数据。 为了实现折叠和展开的功能,可以在TreeView的节点上使用ToggleButton等控件,以便用户点击时切换折叠或展开状态。在代码中,可以使用数据绑定来动态地添加和删除TreeView节点,以及相应地调整DataGrid的可见性。当用户点击折叠按钮时,可以通过修改绑定的标志位来隐藏对应的DataGrid,从而实现折叠效果。 此外,还可以通过自定义样式和模板来美化和定制DataGrid和TreeView的外观。可以修改控件的背景、边框、字体等属性,以便与应用程序的整体风格保持一致。 总之,在WPF实现折叠DataGrid需要合理运用控件和代码逻辑,通过数据绑定和样式调整,来实现折叠和展开的功能,同时保持应用程序的美观和易用性。 ### 回答2: WPF中的DataGrid控件是一个非常强大和灵活的控件,可以用于展示和编辑数据。如果要实现折叠(DataGrid Grouping),我们可以通过使用CollectionViewSource和GroupDescription来实现。 首先,我们需要创建一个CollectionViewSource对象,并将DataGrid的ItemsSource绑定到该对象上。CollectionViewSource允许我们对数据进行分组和排序。 然后,我们可以使用GroupDescription对象来指定分组的属性。GroupDescription可以是一个字符串,表示要根据某个属性进行分组,也可以是一个自定义的实现了IGrouping接口的对象。 最后,我们还需要设置DataGrid的GroupStyle以定义分组的样式。GroupStyle可以包含一个HeaderTemplate,用于显示分组的标题,以及一个ItemsPanel,用于显示分组的内容。 下面是一个简单的示例,展示如何在WPF中折叠(DataGrid Grouping): 1. XAML代码: ```xml <Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="450" Width="800"> <DataGrid x:Name="dataGrid"> <DataGrid.GroupStyle> <GroupStyle> <GroupStyle.HeaderTemplate> <DataTemplate> <TextBlock FontWeight="Bold" Text="{Binding Path=Name}"/> </DataTemplate> </GroupStyle.HeaderTemplate> <GroupStyle.ContainerStyle> <Style TargetType="{x:Type GroupItem}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type GroupItem}"> <StackPanel> <TextBlock FontWeight="Bold" Text="{Binding Path=Items.Count}"/> <ItemsPresenter/> </StackPanel> </ControlTemplate> </Setter.Value> </Setter> </Style> </GroupStyle.ContainerStyle> </GroupStyle> </DataGrid.GroupStyle> </DataGrid> </Grid> </Window> 2. C#代码: ```C# public partial class MainWindow : Window public MainWindow() InitializeComponent(); List<Person> people = new List<Person> new Person{Name="Mike", Age=25}, new Person{Name="Tom", Age=30}, new Person{Name="Mike", Age=35}, new Person{Name="Tom", Age=40} CollectionViewSource cvs = new CollectionViewSource(); cvs.Source = people; cvs.GroupDescriptions.Add(new PropertyGroupDescription("Name")); dataGrid.ItemsSource = cvs.View; public class Person public string Name { get; set; } public int Age { get; set; } 以上代码创建了一个简单的Window,其中有一个DataGrid控件,展示了一个包含姓名和年龄的Person类的集合。通过设置GroupStyle,我们让DataGrid根据姓名进行分组,每个分组显示一个标题和该分组中的数据。 总结起来,WPFDataGrid控件可以通过使用CollectionViewSource和GroupDescription来实现折叠或分组的效果。配合适当的样式设置,我们可以根据自己的需求,灵活地展示和编辑数据。 ### 回答3: WPF折叠DataGrid是一种在用户界面中显示表格数据的方法,可以通过折叠视图以及展开视图来管理和显示大量的数据。 折叠DataGrid可以通过使用WPF的Expander控件来实现。Expander控件提供了一个可折叠的容器,用户可以单击标题来展开或折叠容器内容。 在折叠DataGrid中,可以将DataGrid放置在Expander控件中。当用户点击折叠DataGrid的标题时,Expander会展开,显示DataGrid的内容;当用户再次点击标题时,Expander会折叠,隐藏DataGrid的内容。 在WPF中,我们可以使用XAML来定义折叠DataGrid。首先,我们创建一个Expander控件,将DataGrid作为其Content。然后,我们可以设置Expander的Header为DataGrid的标题。最后,我们可以通过设置Expander的IsExpanded属性来控制DataGrid的展开和折叠。 下面是一个简单的WPF折叠DataGrid的示例: ```xaml <Expander Header="DataGrid" IsExpanded="False"> <DataGrid> <!-- DataGrid的列和数据绑定 --> </DataGrid> </Expander> </Grid> 在这个示例中,DataGrid被放置在Expander内,并将Expander的Header设置为"DataGrid"。IsExpanded属性设置为False,表示DataGrid默认处于折叠状态。 通过这种方式,我们可以方便地实现DataGrid的折叠和展开,以便在需要时显示或隐藏大量的数据。这在处理大量数据或需要用户手动展开来查看数据时非常有用。