目录
介绍
这将为您提供一个很好的datagrid,它能够添加汇总数值的页脚列。我还在列标题上添加了第三次单击以删除排序。使用datagrid时,我默认autogeneratecolumns为false,因为它正在寻找自定义列类型。如果您想更改它,可以更改一些代码。
使用代码
以下是我使用的三个类以及您要获取的聚合类型的enum。
public class CustomGrid : DataGrid
{
public CustomGrid()
{
this.AutoGenerateColumns = false;
}
private Grid FooterGrid;
private List<Border> FootersBlocks;
private Border FooterBorder;
private ScrollViewer ScrollViewer;
private DataGridColumnHeadersPresenter Headers;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.CanUserReorderColumns = false;
FootersBlocks = new List<Border>();
var temp = this.Template;
ScrollViewer = (ScrollViewer) temp.FindName("DG_ScrollViewer", this);
FooterBorder = (Border) temp.FindName("FooterBorder", this);
ScrollViewer.ApplyTemplate();
this.ApplyTemplate();
Headers = (DataGridColumnHeadersPresenter)
ScrollViewer.Template.FindName("PART_ColumnHeadersPresenter", ScrollViewer);
FooterGrid = (Grid)temp.FindName("FooterGrid", this);
this.LayoutUpdated += CustomGrid_LayoutUpdated;
this.PreviewMouseUp += CustomGrid_PreviewMouseUp;
}
protected override void OnSorting(DataGridSortingEventArgs eventArgs)
{
//See if we are sorted in descending order by this column already,
//if so then remove the sorting.
if (eventArgs.Column.SortDirection != null &&
eventArgs.Column.SortDirection == System.ComponentModel.ListSortDirection.Descending)
{
var view = CollectionViewSource.GetDefaultView(this.ItemsSource);
view?.SortDescriptions.Clear();
eventArgs.Column.SortDirection = null;
eventArgs.Handled = true;
return;
}
base.OnSorting(eventArgs);
}
private void CustomGrid_PreviewMouseUp(object sender,
System.Windows.Input.MouseButtonEventArgs e)
{
DependencyObject dep = (DependencyObject) e.OriginalSource;
// iteratively traverse the visual tree
while ((dep != null) && !(dep is DataGridCell) && !(dep is DataGridColumnHeader))
{
dep = VisualTreeHelper.GetParent(dep);
}
//Since we didn't find a cell then maybe a blank row was clicked, see if we can find it
if (dep == null)
{
dep = (DependencyObject)e.OriginalSource;
while ((dep != null) && !(dep is DataGridRow))
{
dep = VisualTreeHelper.GetParent(dep);
}
if (dep == null)
return;
if (dep is DataGridRow)
{
SelectedIndex = this.ItemContainerGenerator.IndexFromContainer((DataGridRow) dep);
}
}
}
private void CustomGrid_LayoutUpdated(object sender, EventArgs e)
{
int index = 0;
double totalColumnWidth = 0;
foreach (CustomGridColumn item in this.Columns)
{
totalColumnWidth += item.ActualWidth;
if (FootersBlocks.Count > index)
{
FootersBlocks[index].Width = item.ActualWidth;
}
index++;
}
double test = Headers.ActualHeight;
double addForScrollBar = 0;
//See if my horizontal scrollbar is visible.
//If so, add some height the top margin of the footer
if (ScrollViewer.ComputedHorizontalScrollBarVisibility == Visibility.Visible)
{
addForScrollBar = 17;
}
FooterBorder.Margin = new Thickness(0, (this.ActualHeight -
(22 + Headers.ActualHeight)) + (ScrollViewer.VerticalOffset - 5) -
addForScrollBar, 0, -10);
}
protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
base.OnItemsSourceChanged(oldValue, newValue);
//I have my own custom view model base with a property changed event
//that I am going to subscribe to so I can update the footer if any of the values change
if (oldValue != null)
{
foreach (var item in oldValue)
{
if (item is FcViewModelBase)
{
var bi = item as FcViewModelBase;
bi.PropertyChanged -= Con_PropertyChanged;
}
}
}
if (newValue != null)
{
foreach (var item in newValue)
{
if (item is FcViewModelBase)
{
var bi = item as FcViewModelBase;
bi.PropertyChanged += Con_PropertyChanged;
}
}
}
}
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
{
base.OnItemsChanged(e);
Update_Footers();
}
public void Update_Footers()
{
FootersBlocks.Clear();
FooterGrid.Children.Clear();
FooterGrid.ColumnDefinitions.Clear();
int index = 0;
foreach (CustomGridColumn item in this.Columns)
{
decimal footerValue = 0;
var tb = new TextBlock();
FooterGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Auto });
if (item.AggregateType != AggregateType.None)
{
foreach (var r in this.Items)
{
System.Type type = r.GetType();
if (type.Name != "NamedObject")
{
string val = type.GetProperty(item.ColumnFooter).GetValue(r, null).ToString();
decimal.TryParse(val, out var fv);
footerValue += fv;
}
}
switch (item.AggregateType)
{
case AggregateType.None:
break;
case AggregateType.Sum:
tb.Text = string.Format(item.StringFormat, footerValue);
break;
case AggregateType.Count:
tb.Text = this.Items.Count.ToString(item.StringFormat);
break;
case AggregateType.Average:
decimal avg = Math.Round(footerValue / this.Items.Count, 2);
tb.Text = string.Format(item.StringFormat, avg);
break;
default:
break;
}
}
tb.VerticalAlignment = VerticalAlignment.Top;
tb.Margin = new Thickness(3);
var borderBrush = (SolidColorBrush)(new BrushConverter().ConvertFrom("#FFB1B1B1"));
var brd = new Border() { BorderThickness = new Thickness(0,0,1,0),
BorderBrush = borderBrush, Width = item.ActualWidth };
brd.Child = tb;
tb.HorizontalAlignment = item.HorizontalAlignment;
brd.SetValue(Grid.ColumnProperty, index);
FooterGrid.Children.Add(brd);
FootersBlocks.Add(brd);
index++;
}
}
private void Con_PropertyChanged(object sender,
System.ComponentModel.PropertyChangedEventArgs e)
{
Update_Footers();
}
}
public class CustomGridColumn : DataGridTextColumn
{
public CustomGridColumn()
{
this.AggregateType = AggregateType.None;
this.StringFormat = "{0}";
}
public static readonly DependencyProperty
ColumnFooterProperty = DependencyProperty.RegisterAttached
("ColumnFooter", typeof(string), typeof(CustomGridColumn));
public string ColumnFooter
{
get { return (string)GetValue(ColumnFooterProperty); }
set { SetValue(ColumnFooterProperty, value); }
}
public static readonly DependencyProperty HorizontalAlignmentProperty =
DependencyProperty.RegisterAttached("HorizontalAlignment",
typeof(HorizontalAlignment), typeof(CustomGridColumn));
public HorizontalAlignment HorizontalAlignment
{
get { return (HorizontalAlignment)GetValue(HorizontalAlignmentProperty); }
set { SetValue(HorizontalAlignmentProperty, value); }
}
public static readonly DependencyProperty StringFormatProperty =
DependencyProperty.RegisterAttached("StringFormat",
typeof(string), typeof(CustomGridColumn));
public string StringFormat
{
get { return (string)GetValue(StringFormatProperty); }
set { SetValue(StringFormatProperty, value); }
}
public static readonly DependencyProperty AggregateTypeProperty =
DependencyProperty.RegisterAttached("AggregateType",
typeof(AggregateType), typeof(CustomGridColumn));
public AggregateType AggregateType
{
get { return (AggregateType)GetValue(AggregateTypeProperty); }
set { SetValue(AggregateTypeProperty, value); }
}
}
public enum AggregateType
{
None, Sum, Count, Average
}
public class FcViewModelBase : INotifyPropertyChanged, IDisposable
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
var propertyChanged = this.PropertyChanged;
if (propertyChanged != null)
{
propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
// free managed resources
}
}
protected bool SetProperty<T>(ref T backingField, T Value,
Expression<Func<T>> propertyExpression)
{
var changed = !EqualityComparer<T>.Default.Equals(backingField, Value);
if (changed)
{
backingField = Value;
this.OnPropertyChanged(ExtractPropertyName(propertyExpression));
}
return changed;
}
private static string ExtractPropertyName<T>(Expression<Func<T>> propertyExpression)
{
var memberExp = propertyExpression.Body as MemberExpression;
if (memberExp == null)
{
throw new ArgumentException("Expression must be a MemberExpression.",
"propertyExpression");
}
return memberExp.Member.Name;
}
}
这是我的主窗口xaml,其中包含我用于自定义数据网格的样式。您必须使用包含的样式,因为它包含页脚边框和网格。
<Window x:Class="WpfApp2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp2"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<SolidColorBrush x:Key="ControlForeground_Normal" Color="#FF000000"/>
<SolidColorBrush x:Key="ControlOuterBorder_Normal" Color="#FFB2B2B2"/>
<SolidColorBrush x:Key="ControlInnerBorder_Normal" Color="#FFFFFFFF"/>
<LinearGradientBrush x:Key="ControlBackground_Normal"
EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="White"/>
<GradientStop Color="#FFE3E8EB" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="ControlBackground_MouseOver"
EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFD6E9F0"/>
<GradientStop Color="#FFA6C8D4" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="ControlBackground_Selected"
EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFD6E9F0"/>
<GradientStop Color="#FFD2EBF3" Offset="1"/>
</LinearGradientBrush>
<SolidColorBrush x:Key="ControlOuterBorder_MouseOver" Color="#FF6F9DB5"/>
<SolidColorBrush x:Key="ControlInnerBorder_MouseOver" Color="#FFFFFFFF"/>
<LinearGradientBrush x:Key="ControlBackground_Pressed"
EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FF8AAEBB" Offset="0"/>
<GradientStop Color="#FFBBDBE6" Offset="0.15"/>
<GradientStop Color="#FF7FBFD4" Offset="1"/>
</LinearGradientBrush>
<SolidColorBrush x:Key="ControlOuterBorder_Pressed" Color="#FF198FB0"/>
<LinearGradientBrush x:Key="ControlInnerBorder_Pressed"
EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FF65A5BB" Offset="0"/>
<GradientStop Color="#FFCCEDF8" Offset="0.143"/>
</LinearGradientBrush>
<SolidColorBrush x:Key="ControlInnerBorder_Disabled" Color="Transparent"/>
<LinearGradientBrush x:Key="ControlBackground_Disabled"
EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFE3E8EB" Offset="1"/>
<GradientStop Color="White"/>
</LinearGradientBrush>
<SolidColorBrush x:Key="ControlOuterBorder_Focused" Color="#FF00B1F0"/>
<SolidColorBrush x:Key="ControlInnerBorder_Focused" Color="Transparent"/>
<SolidColorBrush x:Key="ControlBackground_Focused" Color="Transparent"/>
<LinearGradientBrush x:Key="GridView_HeaderBackground"
EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FF0E7094" Offset="1"/>
<GradientStop Color="#FF1990B1"/>
</LinearGradientBrush>
<ControlTemplate x:Key="DataGridTemplate1" TargetType="{x:Type local:CustomGrid}">
<Border Background="{TemplateBinding Background}"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
Padding="{TemplateBinding Padding}" SnapsToDevicePixels="True">
<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>
<Button Command="{x:Static DataGrid.SelectAllCommand}"
Focusable="false" Style="{DynamicResource
{ComponentResourceKey ResourceId=DataGridSelectAllButtonStyle,
TypeInTargetAssembly={x:Type DataGrid}}}"
Visibility="{Binding HeadersVisibility,
Converter={x:Static DataGrid.HeadersVisibilityConverter},
ConverterParameter={x:Static DataGridHeadersVisibility.All},
RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"
Width="{Binding CellsPanelHorizontalOffset,
RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"/>
<DataGridColumnHeadersPresenter x:Name="PART_ColumnHeadersPresenter"
Grid.Column="1" Visibility="{Binding HeadersVisibility,
Converter={x:Static DataGrid.HeadersVisibilityConverter},
ConverterParameter={x:Static DataGridHeadersVisibility.Column},
RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"/>
<ScrollContentPresenter x:Name="PART_ScrollContentPresenter"
Grid.ColumnSpan="2" CanContentScroll="{TemplateBinding CanContentScroll}"
Grid.Row="1"/>
<ScrollBar x:Name="PART_VerticalScrollBar" Grid.Column="2"
Maximum="{TemplateBinding ScrollableHeight}"
Orientation="Vertical" Grid.Row="1"
ViewportSize="{TemplateBinding ViewportHeight}"
Value="{Binding VerticalOffset, Mode=OneWay,
RelativeSource={RelativeSource TemplatedParent}}"
Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"/>
<Grid Grid.Column="1" Grid.Row="2">
<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"
ViewportSize="{TemplateBinding ViewportWidth}"
Value="{Binding HorizontalOffset, Mode=OneWay,
RelativeSource={RelativeSource TemplatedParent}}"
Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"/>
</Grid>
</Grid>
</ControlTemplate>
</ScrollViewer.Template>
<Grid>
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
Margin="0,0,0,25"/>
<Border Height="25" VerticalAlignment="Top" x:Name="FooterBorder"
BorderBrush="#FFB1B1B1" BorderThickness="1">
<Border.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="White"/>
<GradientStop Color="#FFE4E4E4" Offset="0.148"/>
</LinearGradientBrush>
</Border.Background>
<Grid x:Name="FooterGrid" Margin="5,0,0,0">
</Grid>
</Border>
</Grid>
</ScrollViewer>
</Border>
</ControlTemplate>
<Style TargetType="DataGridRow">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{StaticResource ControlBackground_Selected}" />
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="{StaticResource ControlBackground_MouseOver}" />
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="DataGridCell">
<Setter Property="Padding" Value="4"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridCell}">
<Border Padding="{TemplateBinding Padding}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
<ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="{StaticResource ControlBackground_MouseOver}"/>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="BorderBrush" Value="Transparent" />
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Group By " VerticalAlignment="Center" />
<ComboBox DisplayMemberPath="StyleGroupItemName" Width="150" Height="30" Margin="2" />
<Button Content="Test" Width="150" Height="30" />
</StackPanel>
<local:CustomGrid ItemsSource="{Binding ItemsList}"
Template="{StaticResource DataGridTemplate1}" Grid.Row="1"
CanUserAddRows="False" SelectionMode="Single" SelectionUnit="FullRow"
HorizontalGridLinesBrush="{StaticResource ControlOuterBorder_Normal}"
VerticalGridLinesBrush="{StaticResource ControlOuterBorder_Normal}"
BorderBrush="Silver">
<local:CustomGrid.Columns>
<local:CustomGridColumn Binding="{Binding Item}" Header="Test Header" />
<local:CustomGridColumn Binding="{Binding TestItem}" Header="Test" />
<local:CustomGridColumn Binding="{Binding Total}" AggregateType="Sum"
HorizontalAlignment="Center" StringFormat="Total: {0:C2}"
ColumnFooter="Total" Header="Total" />
</local:CustomGrid.Columns>
</local:CustomGrid>
</Grid>
</Window>
在这个例子中,我只是为我的DataContext使用后面的主窗口代码和一个示例类来用一些测试数据填充数据网格。
public MainWindow()
{
InitializeComponent();
ItemsList = new List<TestClass>();
for (int i = 0; i < 50; i++)
{
var vm = new TestClass { Item = "Test", TestItem = "Test2", Total = 123.5m * i };
ItemsList.Add(vm);
}
this.DataContext = this;
}
public List<TestClass> ItemsList { get; set; }
}
public class TestClass : FcViewModelBase
{
public string Item { get; set; }
public string TestItem { get; set; }
private decimal _total;
public decimal Total
{
get { return _total; }
set { SetProperty(ref _total, value, () => Total); }
}
}
我希望这能让下一个人更容易,当水平或垂直滚动条出现时,尝试让我的网格线看起来漂亮,同时也让我的页脚表现正确。
https://www.codeproject.com/Tips/5299459/WPF-Datagrid-with-Footer-Aggregate