WPF DataGrid table表格强大功能,样式的实现之合计栏

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

1.效果展示

实现wpf datagrid的合计栏,标题头搜索,分页,自定义滚动条, 话不多说 先上效果

WPF DataGrid table表格强大功能,样式的实现之合计栏_第1张图片
标题头筛选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>
                            <Grid.RowDefinitions>
                                <RowDefinition/>
                                <RowDefinition Height="auto"/>
                            </Grid.RowDefinitions>
                            <ScrollViewer x:Name="DG_ScrollViewer" Focusable="false">
                                <ScrollViewer.Template>
                                    <ControlTemplate TargetType="{x:Type ScrollViewer}">
                                        <Grid>
                                            <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}">
                                <Grid>
                                    <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业务代码)
WPF DataGrid table表格强大功能,样式的实现之合计栏_第2张图片

(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 });
                                    }
                                    else
                                    {
                                        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
    {
        /// 
        /// 内容
        /// 
        public string Content { get; set; }
        /// 
        /// 宽度
        /// 
        public double Width { get; set; }
        /// 
        /// 排序
        /// 
        public int Index { get; set; } 
        /// 
        /// 标题头
        /// 
        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内存运行流畅,高于此配置均能完美运行

4.代码

gitee地址

你可能感兴趣的:(wpf相关,wpf,c#,前端)