在多行条件下格式化WPF DataGrid

时间:2014-10-29 16:45:14

标签: c# wpf wpfdatagrid

我有一个WPF DataGrid,包含这样的数据

Number  | Attribute  | Old     | New         |
=============================================|
1       | Height     | 1.1     | 0.9         |
--------+------------+---------+-------------|
1       | Material   | Steel1  | Steel2      |
--------+------------+---------+-------------|
2       | Color      | Green   | Light-Green |
--------+------------+---------+-------------|

由于前两个记录属于同一个Number我想删除两条记录之间的边界,所以它看起来像这样

Number  | Attribute  | Old     | New         |
=============================================|
1       | Height     | 1.1     | 0.9         |
1       | Material   | Steel1  | Steel2      |
--------+------------+---------+-------------|
2       | Color      | Green   | Light-Green |
--------+------------+---------+-------------|

我有一种在加载时格式化行的方法

private void myGrid_LoadingRow(object sender, DataGridRowEventArgs e) {
        ...
}

但是这只能在这一行的数据上格式化,而且我不知道之后或之前的哪一行。所以我无法决定如何格式化此行的边框。

如何根据不仅当前行但上一行和后续行的信息来格式化行?

2 个答案:

答案 0 :(得分:5)

我编写了一个简单的示例应用程序,它只有一个XAML文件和代码隐藏。要重新创建我已经完成的工作,只需创建一个新的WPF 4.5应用程序,然后将下面的代码粘贴到正确的文件中。

我的解决方案使用视图模型,它允许您使用数据绑定执行所有操作(并且不需要您在代码隐藏中连接事件)。

这似乎比您预期的代码要多得多,但请记住,这是一个完整的示例,其中很多只是设置。对于真正重要的代码,希望你会发现,即使它增加了相当多的行,它为你提供了一个非常强大的模板,用于在WPF中创建各种很酷的用户界面。我在每个代码文件后添加了一些注释,希望能让它更容易理解代码的作用。

<强> MainWindow.xaml

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:wpfApplication1="clr-namespace:WpfApplication1"
        mc:Ignorable="d"
        Title="MainWindow"
        Height="350"
        Width="525"
        d:DataContext="{d:DesignInstance Type=wpfApplication1:MainViewModel, IsDesignTimeCreatable=False}">
    <DataGrid AutoGenerateColumns="False"
              ItemsSource="{Binding AttributeUpdateViewModels}"
              GridLinesVisibility="Vertical">
        <DataGrid.RowStyle>
            <Style TargetType="DataGridRow">
                <Setter Property="BorderThickness"
                        Value="{Binding BorderThickness}" />
                <Setter Property="BorderBrush"
                        Value="Black" />
            </Style>
        </DataGrid.RowStyle>
        <DataGrid.Columns>
            <DataGridTextColumn Header="Number"
                                Binding="{Binding Number}" />
            <DataGridTextColumn Header="Attribute"
                                Binding="{Binding Attribute}" />
            <DataGridTextColumn Header="Old"
                                Binding="{Binding Old}" />
            <DataGridTextColumn Header="New"
                                Binding="{Binding New}" />
        </DataGrid.Columns>
    </DataGrid>
</Window>

这基本上只是一个带有文本列的简单数据网格。神奇的是自定义行样式,可根据需要创建水平网格线。 (有关数据绑定的更多详细信息,请参见下文。)

MainWindow.xaml.cs(即代码隐藏):

using System.Collections.Generic;
using System.Linq;
using System.Windows;

namespace WpfApplication1
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = new MainViewModel();
        }
    }

    public class MainViewModel
    {
        public List<AttributeUpdateViewModel> AttributeUpdateViewModels { get; set; }

        public MainViewModel()
        {
            var rawAttributeUpdates = new[]
            {
            new AttributeUpdate { Number = 1, Attribute = "Height", Old = "1.1", New = "0.9" },
            new AttributeUpdate { Number = 1, Attribute = "Material", Old = "Steel1", New = "Steel2" },
            new AttributeUpdate { Number = 2, Attribute = "Color", Old = "Green", New = "Light-Green" },
            new AttributeUpdate { Number = 3, Attribute = "Attribute4", Old = "Old4", New = "New4" },
            new AttributeUpdate { Number = 3, Attribute = "Attribute5", Old = "Old5", New = "New5" },
            new AttributeUpdate { Number = 3, Attribute = "Attribute6", Old = "Old6", New = "New6" },
            new AttributeUpdate { Number = 4, Attribute = "Attribute7", Old = "Old7", New = "New7" },
            new AttributeUpdate { Number = 5, Attribute = "Attribute8", Old = "Old8", New = "New8" },
            new AttributeUpdate { Number = 5, Attribute = "Attribute9", Old = "Old9", New = "New9" },
            new AttributeUpdate { Number = 1, Attribute = "Attribute10", Old = "Old10", New = "New10" }
            };
            var sortedAttributeUpdates = rawAttributeUpdates.OrderBy(x => x.Number);
            var groupedAttributeUpdates = sortedAttributeUpdates
                .GroupBy(x => x.Number);
            AttributeUpdateViewModels = sortedAttributeUpdates
                .Select(x => GetAttributeUpdateRow(x, groupedAttributeUpdates))
                .ToList();
        }

        private AttributeUpdateViewModel GetAttributeUpdateRow(
            AttributeUpdate attributeUpdate,
            IEnumerable<IGrouping<int, AttributeUpdate>> groupedAttributeUpdates)
        {
            var lastInGroup = groupedAttributeUpdates.Single(x => x.Key == attributeUpdate.Number).Last();
            return new AttributeUpdateViewModel
            {
                Number = attributeUpdate.Number,
                Attribute = attributeUpdate.Attribute,
                New = attributeUpdate.New,
                Old = attributeUpdate.Old,
                IsLastInGroup = attributeUpdate == lastInGroup
            };
        }
    }

    public class AttributeUpdate
    {
        public int Number { get; set; }
        public string Attribute { get; set; }
        public string Old { get; set; }
        public string New { get; set; }
    }

    public class AttributeUpdateViewModel
    {
        public int Number { get; set; }
        public string Attribute { get; set; }
        public string Old { get; set; }
        public string New { get; set; }

        public bool IsLastInGroup { get; set; }

        public Thickness BorderThickness
        {
            get { return IsLastInGroup ? new Thickness(0, 0, 0, 1) : new Thickness(); }
        }
    }
}

基本上,我假设您在表格的每一行中显示的数据都是AttributeUpdate。 (我刚刚做了,你可能有一个更好的名字。)

由于AttributeUpdate是纯数据而与格式化数据无关,因此我创建了AttributeUpdateViewModel来合并所需的数据格式信息显示目的。

因此,AttributeUpdateAttributeUpdateViewModel共享相同的数据,但视图模型添加了一些处理格式化的属性。

用于格式化的新属性是什么?

  • IsLastInGroup - 相关行是否是其组中的最后一行(组中的所有项目共享相同的Number)。
  • BorderThickness - 边界的Thickness。在这种情况下,如果项目在组中的最后一个,则为底部边框为1,其他所有项为零,否则为0。

数据绑定在XAML文件中显示为{Binding name_of_property},只需点击视图模型中的数据和格式信息即可。如果基础数据在应用程序运行期间发生变化,您将希望视图模型实现INotifyPropertyChanged interfaceINotifyPropertyChanged基本上会添加&#34;更改检测&#34;到您的应用程序,允许您的绑定自动重新绑定到新的/更改的数据。

最后一点是我使用LINQ query来处理分组逻辑。此特定查询按Number对行进行排序,然后按Number对其进行分组。然后,它会创建AttributeUpdateViewModel个实例,根据当前IsLastInGroup是否与其组中的最后一项匹配来填充AttributeUpdate

注意:为了简单起见,我将几个类放在一个文件中。通常的约定是每个文件一个类,因此您可能希望将每个类分解为自己的文件。

结果

A DataGrid with grid lines between groups rather than between every row

修改

@Mike Strobel的评论指出按数字排序可能不一定是可取的。例如,用户可能希望按其他列排序,但仍会看到按Number分组的行。我不确定这是一个常见的用例,但如果这是一个要求,你可以简单地用一个不同的LINQ查询来代替&#34; current&#34;值&#34; next&#34;值,然后确定Number是否更改。以下是我的解答:

var nextAttributeUpdates = rawAttributeUpdates
    .Skip(1)
    .Concat(new[] { new AttributeUpdate { Number = -1 } });
AttributeUpdateViewModels = rawAttributeUpdates
    .Zip(
        nextAttributeUpdates, 
        (c, n) => new { Current = c, NextNumber = n.Number })
    .Select(
        x => new AttributeUpdateViewModel
        {
            Number = x.Current.Number,
            Attribute = x.Current.Attribute,
            New = x.Current.New,
            Old = x.Current.Old,
            IsLastInGroup = x.Current.Number != x.NextNumber
        })
    .ToList();

答案 1 :(得分:2)

如果您只想隐藏具有相同ID的行后面的行的下边框,那么为什么不将当前行模型与下一行模型进行比较?

private void myGrid_LoadingRow(object sender, DataGridRowEventArgs e)
{
    DataGrid grid = (DataGrid)sender;
    object rowModel = e.Row.Item;
    int index = grid.Items.IndexOf(e.Row.Item);

    bool hideBottomBorder = false;
    if (index + 1 < grid.Items.Count)
    {
        var thisItem = rowModel as TheRowModel;
        var nextItem = grid.Items[index + 1] as TheRowModel;
        if (thisItem.Number == nextItem.Number)
        {
            hideBottomBorder = true;
        }
    }

    if (hideBottomBorder)
    {
        // hide bottom border
    }
    else
    {
        // show bottom border
    }
}

如果集合是固定的(即,没有添加或删除单个项目),上面应该可以正常工作,即使它被重新订购(因为&#34; LoadingRow&#34;将再次为该行中的每一行开火)案件)。如果可以在您的方案中修改单个行,那该怎么办?

  • 如果&#34; Number&#34;已修改:最好在视图模型的PropertyChanged上监听,或者您可以使用DataGrid的CellEditEnding事件。
  • 如果添加或删除了各个行:收听基础集合上的CollectionChanged事件,或使用DataGrid的AddingNewItem / UnloadingRow事件。

您只需触发重新计算与修改行相邻的行的适当边框。