问题描述

WPF自带的ListView和DataGrid控,都提供了数据分组的支持,并可以对分组的Header进行自定义。但是,如果想在每个分组的Header中,显示出本分组的"小计"就不是一件容易的事情了。

假设要用一个ListView用于显示全校学生成绩。按班级分组,并在分组头中显示班级平均分。

最终效果大致如下:

图1. 在分组的Header中显示本分组的Aggregation

怎么样?有什么思路?实现的难点有:

  1. Group Header中的第一例显示为分组的名称。
  2. Group Header中的其它列与数据一致。
  3. Group Header中各列的宽度与ListView中列对应始终一致。
  4. 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:

  1. 每个Setter都有什么作用?删除每一个都有什么不同后果?哪些是可以删除的?(用时5分钟)
  2. 使用TemplateBinding与直接在ControlTemplate中写值有什么不同?(30秒)
  3. 为什么把BorderThickness绑定给了Path,而不是Border?(30秒)
  4. Style中的Trigger能否放在ControlTemplate.Triggers中?为什么使用Style Trigger而不是ControlTemplate Trigger?(5分钟)
  5. 如果你不会StreamGeometry中的语法,那么尝试解读StreamGeometry中的语法(其中Z表示曲线向原点闭合),并写出一个+ - 号风格的ToggleButton Style。(15分钟)

如果只是要方案。这里是完整的代码

http://www.cnblogs.com/nankezhishi/archive/2012/09/04/groupsummaryinlistview.html

在ListView的GroupItem头中显示每列的Summary的更多相关文章

  1. mysql数据库导出模型到powerdesigner,PDM图形窗口中显示数据列的中文注释

    1,mysql数据库导出模型到powerdesigner 2,CRL+Shift+X 3,复制以下内容,执行 '******************************************** ...

  2. 转 在PowerDesigner的PDM图形窗口中显示数据列的中文注释

    Name是名称(字段描述),Code是字段名称,Comment是注释名称,ER图中显示的是Name.一般设计时,Name跟comment都设计成描述, 而设计时候常把comment写成中文,name保 ...

  3. listview的gridview视图中,获取列中模板内的button按钮(找控件内的控件)

    点击“间隙”,获取“间隙”旁边隐藏的减号按钮(本图片未显示出来) private void TextBlock_MouseDown_2(object sender, MouseButtonEventA ...

  4. C#在listview控件中显示数据库数据

    一.了解listview控件的属性 view:设置为details columns:设置列 items:设置行 1.将listview的view设置为details 2.设置列属性 点击添加,添加一列 ...

  5. 提高安全性而在HTTP响应头中可以使用的各种响应头字段

    本文介绍在Web服务器做出响应时,为了提高安全性而在HTTP响应头中可以使用的各种响应头字段.由于部分浏览器中有可能对某些字段或选项不提供支持,所以在使用这些字段时请先确认客户端环境. X-Frame ...

  6. Android(java)学习笔记186:对ListView等列表组件中数据进行增、删、改操作

    1.ListView介绍 解决大量的相似的数据显示问题 采用了MVC模式: M: model (数据模型) V:  view  (显示的视图) C: controller 控制器 入门案例: acit ...

  7. Http 请求头中的 Proxy-Connection

    平时用 Chrome 开发者工具抓包时,经常会见到 Proxy-Connection 这个请求头.之前一直没去了解什么情况下会产生它,也没去了解它有什么含义.最近看完<HTTP 权威指南> ...

  8. android 在你的UI中显示Bitmap - 开发文档翻译

    由于本人英文能力实在有限,不足之初敬请谅解 本博客只要没有注明“转”,那么均为原创,转贴请注明本博客链接链接 Displaying Bitmaps in Your UI 在你的UI中显示Bitmap ...

  9. tableView区头不显示

    不知道什么原因 如果设置tableView的样式为Group 则必须写代理 p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Menlo; co ...

随机推荐

  1. 【spring boot】集成了druid后,同样的mybatis模糊查询语句出错Caused by: com.alibaba.druid.sql.parser.ParserException: syntax error, error in :'name LIKE '%' ? '%'

    druid版本是 <!-- https://mvnrepository.com/artifact/com.alibaba/druid 数据库连接池--> <dependency> ...

  2. kubernetes1.5.2--部署node-problem-detector服务

    本文基于kubernetes 1.5.2版本编写 node经常会遇到以下问题: 硬件问题: cpu 内存 磁盘 内核问题: 内核死锁, 文件系统损坏 容器问题: 守护进程无响应 K8S集群管理对nod ...

  3. scp拷贝本地文件到服务器

    拷贝远程服务器的文件到本地:scp -r -P  端口号   用户名@IP地址:/usr/local/tomcat_airc/webapps/        /tmp/kyj/ 拷贝本地文件到远程服务 ...

  4. [Algorithm] Median Maintenance algorithm implementation using TypeScript / JavaScript

    The median maintenance problem is a common programming challenge presented in software engineering j ...

  5. cocos2d-x ios游戏开发初认识(六) 渲染的优化

    做程序开发肯定要考虑到内存的优化,毕竟iphone本身的内存就不是非常大.这一节主要说这个cocos2d开发对内存的优化,详细表如今,既能够对同样的精灵(图片)仅仅渲染一次,也能够对不能的精灵仅仅渲染 ...

  6. 倍福TwinCAT(贝福Beckhoff)基础教程2.2 TwinCAT常见类型使用和转换_枚举

    在Duts的文件夹上右击,可以声明一个枚举类型,按照格式填写所有类型(注意枚举的元素前面都是逗号,最后一个不需要符号)   在正常使用的时候,枚举的单词可以当全局变量来用     更多教学视频和资料下 ...

  7. NSDate 类的总结,全面基础

    <span style="font-size:24px;"><span style="font-size:18px;">//1.创建日期 ...

  8. (五)Thymeleaf标准表达式之——[7->8]条件表达式& 默认表达式

    2.7 条件表达式 模板名称:condition-express.html <1>a ? b:c  (if then:else) <2>a?c (if else) 条件表达式( ...

  9. const readonly

    静态常量(compile-time constants)静态常量是指编译器在编译时候会对常量进行解析,并将常量的值替换成初始化的那个值. 动态常量(runtime constants)而动态常量的值则 ...

  10. 转:HTTP协议--- multipart/form-data请求分析

    转自:http://blog.csdn.net/five3/article/details/7181521 首先来了解什么是multipart/form-data请求: 根据http/1.1 rfc ...