2014-10-29 71 views
2

我有一个WPF DataGrid中,像这样格式WPF的DataGrid的多行条件

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

数据。由于前2条记录属于因一起同Number我想2个记录之间删除边框等等它看起来像这样

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) { 
     ... 
} 

但是,这只能格式化这个行的数据,我不知道哪一行在之后。所以我不能决定如何格式化这一行的边框。

如何根据当前行的信息而不是当前行的信息对行进行格式化?

+0

当使用标准的'DataGrid'时没有像这样合并行的直接方式。有一些第三方网格可能支持这种事情,但如果您尝试一起破解解决方案,如果您开始让用户应用排序,分组或过滤,将会变成一场噩梦。 – 2014-10-29 20:55:32

+1

@MikeStrobel:但我不想合并行。我只想在前面和后面的记录上格式化它们 – 2014-10-29 20:57:16

+0

它们是否实际上“合并”并不是真正的重点。你想根据相邻行是否共享数据来有条件地格式化行,这将比你想象的要复杂得多。行可以基于排序和过滤被添加,移除和重新排列,更不用说为行虚拟化而回收。 – 2014-10-29 20:59:45

回答

4

我写了一个简单的示例应用程序,只有一个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基本上将“更改检测”添加到您的应用程序,允许您的绑定自动重新绑定到新的/更改的数据。最后一点是我用LINQ query来照顾分组逻辑。此特定查询按Number排序行,然后按Number对它们进行分组。然后,它创建AttributeUpdateViewModel实例,根据当前AttributeUpdate是否与其组中的最后一项匹配来填充IsLastInGroup

注:为了简单起见,我在一个文件中放了几个类。通常的约定是每个文件一个类,所以你可能想要将每个类分成它自己的文件。

结果

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

编辑

@Mike斯特罗贝尔的评论使点按号码排序未必是可取的。例如,用户可能希望按不同的列进行排序,但仍然可以看到按数字分组的行。我不确定这会是一个常见用例,但如果这是一项要求,您可以简单地用不同的LINQ查询来替换“当前”值与“下一个”值,然后确定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(); 
+0

您正在从* unsorted *'rawAttributeUpdates'序列中填充'AttributeUpdateViewModels',如果原始列表尚未按数字排序,则会给出错误的结果。复制集合初始值设定项中的项目块以查看我的意思。由于此解决方案依赖于按数字对源集合进行排序,只要用户按其他列进行排序,该解决方案就会中断。 – 2014-11-06 15:29:58

+0

@MikeStrobel,谢谢,两个很好的渔获。我修改了查询,以便数据先按数字排序,然后根据排序的数据进行分组和选择。我还在我的答案底部编辑了一个新查询,以解决用户通过其他列进行排序的可能性。 – devuxer 2014-11-06 18:48:31

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 
    } 
} 

以上应该可以正常工作,如果集合是固定的(即单个项目不添加或删除),即使它被重新排序(因为“LoadingRow”将在这种情况下,再次激发每一行)。如果在您的场景中可以修改单个行,那该怎么办?

  • 如果“号码”被修改:最好听这在视图模型的PropertyChanged,或者你可以使用DataGrid的CellEditEnding事件。
  • 如果添加或删除单个行:监听基础集合上的CollectionChanged事件,或者使用DataGrid的事件​​/UnloadingRow事件。

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