在ListView的GroupItem头中显示每列的Summary
问题描述
WPF自带的ListView和DataGrid控,都提供了数据分组的支持,并可以对分组的Header进行自定义。但是,如果想在每个分组的Header中,显示出本分组的"小计"就不是一件容易的事情了。
假设要用一个ListView用于显示全校学生成绩。按班级分组,并在分组头中显示班级平均分。
最终效果大致如下:

图1. 在分组的Header中显示本分组的Aggregation
怎么样?有什么思路?实现的难点有:
- Group Header中的第一例显示为分组的名称。
- Group Header中的其它列与数据一致。
- Group Header中各列的宽度与ListView中列对应始终一致。
- Group Header 中各列的对齐方式与ListView中各列一致。
数据
Model层只有一个类,ScoreInfo,代码如下:
public class ScoreInfo
{
public ScoreInfo(string studentNo, string className, int math, int english)
{
StudentNo = studentNo;
ClassName = className;
MathScore = math;
EnglishScore = english;
} public string StudentNo { get; set; } public string ClassName { get; set; } public int MathScore { get; set; } public int EnglishScore { get; set; } [ReadOnly(true)]
public int TotalScore
{
get { return MathScore + EnglishScore; }
}
}
一次考试中,学生的这些数据都不会变。所以不需要实现INotifyPropertyChanged接口。
DAL层直接返回假数据,代码如下:
public class SchoolScoreProvider
{
public List<ScoreInfo> ReadAllScoreInfo()
{
var random = new Random();
return (from i in Enumerable.Range(0, 4)
let className = String.Format("({0}) 班", i + 1) from s in Enumerable.Range(i * 6 + 1, 6)
let mScore = random.Next(101)
let eScore = random.Next(101) select new ScoreInfo(s.ToString(), className, mScore, eScore))
.ToList();
}
}
在XAML中声明(实体化)数据源,代码如下:
<!-- Prepare Data Source -->
<!-- Just for demo, DON'T initialize your Data Source this way in real project. -->
<ObjectDataProvider x:Key="Data"
ObjectType="{x:Type l:SchoolScoreProvider}"
MethodName="ReadAllScoreInfo" />
<CollectionViewSource x:Key="DataView"
Source="{StaticResource Data}">
<!-- Group By ClassName -->
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="ClassName" />
</CollectionViewSource.GroupDescriptions>
<!-- Sort By TotalScore -->
<CollectionViewSource.SortDescriptions>
<c:SortDescription PropertyName="TotalScore" Direction="Descending" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
UI层代码
然后是ListView的定义:
<ListView DataContext="{StaticResource DataView}"
ItemsSource="{Binding}">
<!-- Specify the current ListView should Group data source items. -->
<ListView.GroupStyle>
<GroupStyle />
</ListView.GroupStyle>
<ListView.View>
<GridView>
<!-- CellTemplate is the only NORMAL way to make the column text right align. -->
<!-- The following code should works with the default ListViewItem style above in Resources.
BUT it doesn't.
<GridViewColumn Header="学号" Width="75"
DisplayMemberBinding="{Binding StudentNo}"
TextBlock.TextAlignment="Right" />
Think, if you need to change the Alignment while running. Their differences will be obvious.
-->
<GridViewColumn Header="学号" Width="75">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding StudentNo}"
TextAlignment="Right" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="数学成绩" Width="75">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding MathScore}"
TextAlignment="Right" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="英语成绩" Width="75">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding EnglishScore}"
TextAlignment="Right" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
为了让Group支持分列,直接能想到的方法就是在其中放置一个ListViewItem。经过尝试这个方案是可行的。需要自定义GroupItem的ControlTemplate。代码如下:
<!-- Custom GroupItem to support group collaps/expand -->
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ListViewItem Style="{StaticResource GroupHeaderListViewItemStyle}"
Content="{Binding Converter={StaticResource GroupDataAggregator}}" />
<ToggleButton Name="expander"
Style="{StaticResource ToggleExpanderStyle}" />
<ItemsPresenter Grid.Row="1"
Visibility="{Binding IsChecked, ElementName=expander, Converter={StaticResource BooleanToVisibilityConverter}}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
其中的关键点是,在里面放了一个ListViewItem。这里直接放GridViewRowPresenter也是可以正确显示出数据的,但是由于没有ListViewItem作Host,每列中的文字会始终向左对齐。
使用一个Converter,对当前组的数据进行Aggregation,生成一个表示班级平均分的ScoreInfo。Convert的代码如下:
public class GroupDataAggregator : IValueConverter
{
public object Convert(object value, Type type, object arg, CultureInfo culture)
{
var groupData = value as CollectionViewGroup;
if (groupData != null)
{
var scores = groupData.Items.Cast<ScoreInfo>();
var avgMath = (int)scores.Average(x => x.MathScore);
var avgEng = (int)scores.Average(x => x.EnglishScore); return new ScoreInfo(groupData.Name.ToString(), null, avgMath, avgEng);
} return new InvalidOperationException();
} public object ConvertBack(object value, Type type, object arg, CultureInfo culture)
{
throw new NotImplementedException();
}
}
当然,这个Convert并不重点。另一个重点是,要为GroupItem中的ListViewItem写一个特殊的Style,否则,什么东西都看不到。
<!-- Style apply to all ListViewItem, to make its cell stretch. -->
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style> <!-- Style apply to the ListViewItem in GroupItem header -->
<Style x:Key="GroupHeaderListViewItemStyle"
TargetType="{x:Type ListViewItem}"
BasedOn="{StaticResource {x:Type ListViewItem}}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<!-- KEY STEP: Binding the Columns to ListView's -->
<GridViewRowPresenter Columns="{Binding View.Columns, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListView}}}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
关键的一步就是给GridViewRowPresenter指定Columns,ListView可不会自动为这个外人设置这个属性的。
再回去讲GroupItem,用于展开GroupItem的是其Template中的ToggleButton,即图中三角图标。其代码如下:
<!-- The toggle button in GroupItem Header, used to expand/collaps a group -->
<Style x:Key="ToggleExpanderStyle"
TargetType="{x:Type ToggleButton}">
<Setter Property="Width" Value="16" />
<Setter Property="Height" Value="16" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="IsChecked" Value="True" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="BorderBrush" Value="Black" />
<Setter Property="Background" Value="Black" />
<Setter Property="Padding" Value="0" />
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
<Setter Property="IsTabStop" Value="False" />
<Setter Property="Content">
<Setter.Value>
<StreamGeometry>M6,0 L6,6 L0,6Z</StreamGeometry>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<!-- The border is used to make the control a rectangle which is easier to click. -->
<Border Background="Transparent"
BorderThickness="0">
<Path Stretch="None"
Data="{TemplateBinding Content}"
Margin="{TemplateBinding Padding}"
Fill="{TemplateBinding Background}"
Stroke="{TemplateBinding BorderBrush}"
StrokeThickness="{TemplateBinding BorderThickness}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#007ACC" />
<Setter Property="BorderBrush" Value="#007ACC" />
</Trigger>
<Trigger Property="IsChecked" Value="False">
<Setter Property="Background" Value="White" />
<Setter Property="Content">
<Setter.Value>
<StreamGeometry>M0,0 L4,4 L0,8Z</StreamGeometry>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
题外话
另给勤奋些、不喜欢吃冷饭的同学几个思考的方向,用于确认对上面代码的理解程度。
针对上面这个ToggleButton的Style:
- 每个Setter都有什么作用?删除每一个都有什么不同后果?哪些是可以删除的?(用时5分钟)
- 使用TemplateBinding与直接在ControlTemplate中写值有什么不同?(30秒)
- 为什么把BorderThickness绑定给了Path,而不是Border?(30秒)
- Style中的Trigger能否放在ControlTemplate.Triggers中?为什么使用Style Trigger而不是ControlTemplate Trigger?(5分钟)
- 如果你不会StreamGeometry中的语法,那么尝试解读StreamGeometry中的语法(其中Z表示曲线向原点闭合),并写出一个+ - 号风格的ToggleButton Style。(15分钟)
如果只是要方案。这里是完整的代码。
http://www.cnblogs.com/nankezhishi/archive/2012/09/04/groupsummaryinlistview.html
在ListView的GroupItem头中显示每列的Summary的更多相关文章
- mysql数据库导出模型到powerdesigner,PDM图形窗口中显示数据列的中文注释
1,mysql数据库导出模型到powerdesigner 2,CRL+Shift+X 3,复制以下内容,执行 '******************************************** ...
- 转 在PowerDesigner的PDM图形窗口中显示数据列的中文注释
Name是名称(字段描述),Code是字段名称,Comment是注释名称,ER图中显示的是Name.一般设计时,Name跟comment都设计成描述, 而设计时候常把comment写成中文,name保 ...
- listview的gridview视图中,获取列中模板内的button按钮(找控件内的控件)
点击“间隙”,获取“间隙”旁边隐藏的减号按钮(本图片未显示出来) private void TextBlock_MouseDown_2(object sender, MouseButtonEventA ...
- C#在listview控件中显示数据库数据
一.了解listview控件的属性 view:设置为details columns:设置列 items:设置行 1.将listview的view设置为details 2.设置列属性 点击添加,添加一列 ...
- 提高安全性而在HTTP响应头中可以使用的各种响应头字段
本文介绍在Web服务器做出响应时,为了提高安全性而在HTTP响应头中可以使用的各种响应头字段.由于部分浏览器中有可能对某些字段或选项不提供支持,所以在使用这些字段时请先确认客户端环境. X-Frame ...
- Android(java)学习笔记186:对ListView等列表组件中数据进行增、删、改操作
1.ListView介绍 解决大量的相似的数据显示问题 采用了MVC模式: M: model (数据模型) V: view (显示的视图) C: controller 控制器 入门案例: acit ...
- Http 请求头中的 Proxy-Connection
平时用 Chrome 开发者工具抓包时,经常会见到 Proxy-Connection 这个请求头.之前一直没去了解什么情况下会产生它,也没去了解它有什么含义.最近看完<HTTP 权威指南> ...
- android 在你的UI中显示Bitmap - 开发文档翻译
由于本人英文能力实在有限,不足之初敬请谅解 本博客只要没有注明“转”,那么均为原创,转贴请注明本博客链接链接 Displaying Bitmaps in Your UI 在你的UI中显示Bitmap ...
- tableView区头不显示
不知道什么原因 如果设置tableView的样式为Group 则必须写代理 p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Menlo; co ...
随机推荐
- C源程序到可执行文件的四个过程
C源程序到可执行文件的四个过程 1.预处理:预编译器执行.譬如C中的宏定义就是由预编译器处理,注释等也是由预编译器处理的 gcc -E -hello.c -o hello.i 2.编译:编译器来执行. ...
- 如何在阿里云服务器搭建FTP服务器,在本地电脑连接并操作
首先你需要有一个阿里云的ECS服务器 并且开通了公网宽带(话说也不贵,开来玩玩还是可以的,第一次买会比较便宜,第二次买1M的宽带两天是九毛多吧~) 开通了宽带之后,ECS服务器就可以上网了 如果嫌弃阿 ...
- Kubernetes - 配置Nginx-Ingress 作为服务发现
添加 Kubernetes ConfigMap配置来自定义端口与服务的映射关系 配置文件, 有二个在默认空间下web服务和api服务分别映射到自定义端口 9001, 9002 apiVersion: ...
- A read-only user or a user in a read-only database is not permitted to disable
A read-only user or a user in a read-only database is not permitted to disable 出现如题的问题通常是由于db.lck的所属 ...
- 系统重装 U盘安装XP操作系统开机出现提示txtsetup.sif怎么办
你的这个问题 是安装xp时把xp做成u盘出现的 原因是xp没有在根本上支持这种安装 到win7后才支持的 解决方法有以下几种 1,刻录成cd 2重新下载xp ghost版的不会出现这个问题 3证实Ul ...
- PS如何使用制作图片投影效果
如果仅仅是同样大小的背景(灰色或者黑色),则只要新建一个和比原图大三个像素的文件(比如这里原图片为100×100,我就建立一个103×103的文件)把图片放在左上角,剩下的部分填充灰色或者黑色.然而这 ...
- vscode - 更改emmet生成代码
有时候生成的代码,并不适用自己,所以想想改生成代码: 因为windows查找文件/文件内容非常慢,所以借用了一下Linux的搜索命令,查找了一下 ie=edge ,最后,找到了 expand-ful ...
- Win8.1应用开发之Bing Maps
这里介绍怎样进行Bing Maps的开发.首先我们须要在我们的程序中引入Bing Map的SDK.详细方法,这里推荐一个链接<win8>使用Bing地图.这样一个hello world便出 ...
- Appium Android Bootstrap源代码分析之简单介绍
在上一个系列中我们分析了UiAutomator的核心源代码,对UiAutomator是怎么执行的原理有了根本的了解.今天我们会開始另外一个在安卓平台上基于UiAutomator的新起之秀--Appiu ...
- JavaScript对象this指向(普通键this指向 非指向函数的键)
1.结论 JavaScript对象普通键(非指向函数的键)this指向是window. 2.示例 <!DOCTYPE html> <html lang="zh"& ...