最近准备做几期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内存运行流畅,高于此配置均能完美运行