zoukankan      html  css  js  c++  java
  • 在ListView的GroupItem头中显示每列的Summary

    问题描述

         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

  • 相关阅读:
    【PowerOJ1736&网络流24题】飞行员配对方案问题(最小割)
    MySQL的锁机制
    Hadoop相关基础知识
    ICMP基础知识
    关于CPU的一些基础知识
    关于内存的一些基础知识
    MySQL查询优化
    MapReduce初探
    压缩解压缩相关基础知识
    后台服务相关基础知识
  • 原文地址:https://www.cnblogs.com/sjqq/p/8343305.html
Copyright © 2011-2022 走看看