最近准备做几期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代码 进行逻辑实现
publicstaticreadonlyDependencyProperty TotalItemsProperty =
DependencyProperty.Register("TotalItems",typeof(object),typeof(DataGrid),newPropertyMetadata(null, TotalItemsCallBack));publicobject OriginItemsSource {get;set;}publicobject ItemsSources
{get{return(object)GetValue(ItemsSourcesProperty);}set{SetValue(ItemsSourcesProperty,value);}}
以上代码(来自DataGrid自定义样式类)是合计栏的数据来源,是一个对象,通过后端接口返回的,当然,也可以由前端自己合计,对TotalItems进行赋值即可,赋值方式如下(来自ViewModel业务代码)
(3)TotalItemsCallBack(合计项赋值后回调)代码分析
privatestaticvoidTotalItemsCallBack(DependencyObject d,DependencyPropertyChangedEventArgs e){DataGrid dataGrid = d asDataGrid;if(e.NewValue !=null&& dataGrid.itemsControl !=null){if(e.NewValue isList<string> totalItems){//数组和对象都要转为数组;//排序问题处理
System.Threading.Tasks.Task.Factory.StartNew(()=>{
System.Threading.Thread.Sleep(1000);
Application.Current.Dispatcher.BeginInvoke((Action)(()=>{List<DataGridTotal> dataGrids =newList<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(newDataGridTotal{ Width = dataGrid.Columns[i].ActualWidth, Content = totalItems[i], Index = dataGrid.Columns[i].DisplayIndex });}else{
dataGrids.Add(newDataGridTotal{ Width = dataGrid.Columns[i].ActualWidth, Content ="-", Index = dataGrid.Columns[i].DisplayIndex });}}}
dataGrids = dataGrids.OrderBy(x => x.Index).ToList();
dataGrid.itemsControl.ItemsSource = dataGrids;}));});}elseif(e.NewValue isobject obj){
System.Threading.Tasks.Task.Factory.StartNew(()=>{
System.Threading.Thread.Sleep(1000);
Application.Current.Dispatcher.BeginInvoke((Action)(()=>{List<DataGridTotal> dataGrids =newList<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(newDataGridTotal{ 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 =newList<DataGridTotal>();for(int i =0; i < dataGrid.Columns.Count; i++){if(dataGrid.Columns[i].Visibility == Visibility.Visible){
dataGrids.Add(newDataGridTotal{ 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类如下:
publicclassDataGridTotal{/// <summary>/// 内容/// </summary>publicstring Content {get;set;}/// <summary>/// 宽度/// </summary>publicdouble Width {get;set;}/// <summary>/// 排序/// </summary>publicint Index {get;set;}/// <summary>/// 标题头/// </summary>publicstring Header {get;set;}}
加入异步是为了减少前端自己合计 数据量大时的卡顿效果
(4)用户拖动标题头,顺序发生改变后逻辑处理
对合计栏的排序进行重新计算
privatevoidDataGrid_ColumnDisplayIndexChanged(object sender,DataGridColumnEventArgs e){if(TotalItems !=null){List<DataGridTotal> dataGrids =newList<DataGridTotal>();if(TotalItems isList<string> items){for(int i =0; i < Columns.Count; i++){if(Columns[i].Visibility == Visibility.Visible){
dataGrids.Add(newDataGridTotal{ Width = Columns[i].ActualWidth, Content = items[i], Index = Columns[i].DisplayIndex });}}}elseif(TotalItems isobject 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(newDataGridTotal{ 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 =newList<DataGridTotal>();for(int i =0; i < Columns.Count; i++){if(Columns[i].Visibility == Visibility.Visible){
dataGrids.Add(newDataGridTotal{ Width = Columns[i].ActualWidth, Content ="-", Index = Columns[i].DisplayIndex });}}
dataGrids = dataGrids.OrderBy(x => x.Index).ToList();
itemsControl.ItemsSource = dataGrids;}}
(5)合计栏宽度自适应
内容发生改变,列宽自动撑开后合计栏宽度的自适应,用户拖动列的宽度后,合计栏的自适应
privatevoidOwnScrrow_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 asScrollViewer).HorizontalOffset);}
3.性能分析
windows xp(客户那里找来的,估计年龄比我还大) 256m内存运行流畅,高于此配置均能完美运行
4.代码
gitee地址
版权归原作者 Ryannn_NN 所有, 如有侵权,请联系我们删除。