最近准备做几期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> <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业务代码)
(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 { /// <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内存运行流畅,高于此配置均能完美运行
4.代码
gitee地址