本文展示了如何通过自定义样式和XAML代码实现WPFDataGrid的合计栏功能,以及标题头的搜索功能。代码示例中详细解释了合计栏的实现流程,包括ItemsControl的使用和宽度计算逻辑,同时也讨论了用户拖动标题头后的排序处理和宽度自适应。文章还提及了性能分析和适用环境,适用于需要对WPFDataGrid进行增强功能开发的开发者。
摘要由CSDN通过智能技术生成
最近准备做几期wpf datagrid优化功能的博客,包括合计栏,标题头带搜索功能,标题头带感叹号的提示框,ui优化等等.本期展示合计栏,后续再添加其他功能代码
实现wpf datagrid的合计栏,标题头搜索,分页,自定义滚动条, 话不多说 先上效果
标题头筛选UI还未调整,请忽略
<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.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<ScrollViewer x:Name="DG_ScrollViewer" Focusable="false">
<ScrollViewer.Template>
<ControlTemplate TargetType="{x:Type ScrollViewer}">
<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}">
<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>
每一项宽度的计算是一个重点,因为每一项的宽度是随内容变化的,同时用户可以拖拽改变每一列的宽度,每次宽度的改变,都得重新计算,这里请注意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业务代码)
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 });
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; }
加入异步是为了减少前端自己合计 数据量大时的卡顿效果
对合计栏的排序进行重新计算
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;
内容发生改变,列宽自动撑开后合计栏宽度的自适应,用户拖动列的宽度后,合计栏的自适应
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);
windows xp(客户那里找来的,估计年龄比我还大) 256m内存运行流畅,高于此配置均能完美运行
gitee地址
根据行数,列数自动创建表格,使用WPF中的Grid很容易实现,并且容易实现单元格的合并拆分。主要需要解决两个问题:
单元格的定位,那一行,那一列,占几行,占几列
单元格的边框
行,列,占行,占列该问题比较容易解决,Grid分行,分列后,添加的控件可以使用Grid.Row,Grid.Column,Grid.RowSpan,Grid.ColumnSpan解决。单元格边框每一个单元格使用一个Border,
wpf的DataGrid中,添加某一列的合计,并在最下方固定显示。本列子不使用ToolKit(有3.5和4.0的限制),添加一个类,重写DataGrid。可以在DataGrid上下左右绑定一个DataGrid,类似Footer属性。示例可直接运行,简单易懂。
(思路:两个DataGrid(mainDG,bottomDG),bottomDG的左右边binding 主表mainDG。并bottomDG绑定数据源(只有一行(合计的值)),),合计行的列宽可随着mainDG的改变而改变。
一、
datagrid继承体系
“想要说些什么 又不知从何说起”,每当想要写一些关于
wpf的文章,总是沉思良久,怕自己写不好。今天我想要说的是
wpf中
datagrid控件。我们先来看看它在整个类的层次结构:
WPF的Grid布局使用起来很方便,但如果我想绘制一个带有边线的表格则显得有点儿力不从心。虽然Grid有ShowGridLines这个bool类型的属性,但此属性设为true时显示的虚线而且不能改变颜色。比如下面的代码:
<Window x:Class="Table.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2...
### 回答1:
WPF(Windows Presentation Foundation)是一种用于创建Windows应用程序界面的框架。在WPF中,可以使用DataGrid控件来展示和编辑数据。折叠DataGrid可以让用户在需要时展开和收起数据,以便更好地组织和管理信息。
要在WPF中实现折叠DataGrid,可以使用一些额外的控件和代码逻辑。首先,可以使用TreeView控件作为外层容器,用于显示可展开和收起的树形结构。然后,在TreeView的各个节点中,嵌套使用DataGrid控件来展示具体的数据。
为了实现折叠和展开的功能,可以在TreeView的节点上使用ToggleButton等控件,以便用户点击时切换折叠或展开状态。在代码中,可以使用数据绑定来动态地添加和删除TreeView节点,以及相应地调整DataGrid的可见性。当用户点击折叠按钮时,可以通过修改绑定的标志位来隐藏对应的DataGrid,从而实现折叠效果。
此外,还可以通过自定义样式和模板来美化和定制DataGrid和TreeView的外观。可以修改控件的背景、边框、字体等属性,以便与应用程序的整体风格保持一致。
总之,在WPF中实现折叠DataGrid需要合理运用控件和代码逻辑,通过数据绑定和样式调整,来实现折叠和展开的功能,同时保持应用程序的美观和易用性。
### 回答2:
WPF中的DataGrid控件是一个非常强大和灵活的控件,可以用于展示和编辑数据。如果要实现折叠(DataGrid Grouping),我们可以通过使用CollectionViewSource和GroupDescription来实现。
首先,我们需要创建一个CollectionViewSource对象,并将DataGrid的ItemsSource绑定到该对象上。CollectionViewSource允许我们对数据进行分组和排序。
然后,我们可以使用GroupDescription对象来指定分组的属性。GroupDescription可以是一个字符串,表示要根据某个属性进行分组,也可以是一个自定义的实现了IGrouping接口的对象。
最后,我们还需要设置DataGrid的GroupStyle以定义分组的样式。GroupStyle可以包含一个HeaderTemplate,用于显示分组的标题,以及一个ItemsPanel,用于显示分组的内容。
下面是一个简单的示例,展示如何在WPF中折叠(DataGrid Grouping):
1. XAML代码:
```xml
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="450" Width="800">
<DataGrid x:Name="dataGrid">
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock FontWeight="Bold" Text="{Binding Path=Name}"/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<StackPanel>
<TextBlock FontWeight="Bold" Text="{Binding Path=Items.Count}"/>
<ItemsPresenter/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
</DataGrid>
</Grid>
</Window>
2. C#代码:
```C#
public partial class MainWindow : Window
public MainWindow()
InitializeComponent();
List<Person> people = new List<Person>
new Person{Name="Mike", Age=25},
new Person{Name="Tom", Age=30},
new Person{Name="Mike", Age=35},
new Person{Name="Tom", Age=40}
CollectionViewSource cvs = new CollectionViewSource();
cvs.Source = people;
cvs.GroupDescriptions.Add(new PropertyGroupDescription("Name"));
dataGrid.ItemsSource = cvs.View;
public class Person
public string Name { get; set; }
public int Age { get; set; }
以上代码创建了一个简单的Window,其中有一个DataGrid控件,展示了一个包含姓名和年龄的Person类的集合。通过设置GroupStyle,我们让DataGrid根据姓名进行分组,每个分组显示一个标题和该分组中的数据。
总结起来,WPF的DataGrid控件可以通过使用CollectionViewSource和GroupDescription来实现折叠或分组的效果。配合适当的样式设置,我们可以根据自己的需求,灵活地展示和编辑数据。
### 回答3:
WPF折叠DataGrid是一种在用户界面中显示表格数据的方法,可以通过折叠视图以及展开视图来管理和显示大量的数据。
折叠DataGrid可以通过使用WPF的Expander控件来实现。Expander控件提供了一个可折叠的容器,用户可以单击标题栏来展开或折叠容器内容。
在折叠DataGrid中,可以将DataGrid放置在Expander控件中。当用户点击折叠DataGrid的标题时,Expander会展开,显示DataGrid的内容;当用户再次点击标题时,Expander会折叠,隐藏DataGrid的内容。
在WPF中,我们可以使用XAML来定义折叠DataGrid。首先,我们创建一个Expander控件,将DataGrid作为其Content。然后,我们可以设置Expander的Header为DataGrid的标题。最后,我们可以通过设置Expander的IsExpanded属性来控制DataGrid的展开和折叠。
下面是一个简单的WPF折叠DataGrid的示例:
```xaml
<Expander Header="DataGrid" IsExpanded="False">
<DataGrid>
<!-- DataGrid的列和数据绑定 -->
</DataGrid>
</Expander>
</Grid>
在这个示例中,DataGrid被放置在Expander内,并将Expander的Header设置为"DataGrid"。IsExpanded属性设置为False,表示DataGrid默认处于折叠状态。
通过这种方式,我们可以方便地实现DataGrid的折叠和展开,以便在需要时显示或隐藏大量的数据。这在处理大量数据或需要用户手动展开来查看数据时非常有用。