我有一个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) {
...
}
但是这只能在这一行的数据上格式化,而且我不知道之后或之前的哪一行。所以我无法决定如何格式化此行的边框。
如何根据不仅当前行但上一行和后续行的信息来格式化行?
答案 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
来合并所需的数据和格式信息显示目的。
因此,AttributeUpdate
和AttributeUpdateViewModel
共享相同的数据,但视图模型添加了一些处理格式化的属性。
用于格式化的新属性是什么?
IsLastInGroup
- 相关行是否是其组中的最后一行(组中的所有项目共享相同的Number
)。BorderThickness
- 边界的Thickness
。在这种情况下,如果项目在组中的最后一个,则为底部边框为1,其他所有项为零,否则为0。数据绑定在XAML文件中显示为{Binding name_of_property}
,只需点击视图模型中的数据和格式信息即可。如果基础数据在应用程序运行期间发生变化,您将希望视图模型实现INotifyPropertyChanged interface。 INotifyPropertyChanged
基本上会添加&#34;更改检测&#34;到您的应用程序,允许您的绑定自动重新绑定到新的/更改的数据。
最后一点是我使用LINQ query来处理分组逻辑。此特定查询按Number
对行进行排序,然后按Number
对其进行分组。然后,它会创建AttributeUpdateViewModel
个实例,根据当前IsLastInGroup
是否与其组中的最后一项匹配来填充AttributeUpdate
。
注意:为了简单起见,我将几个类放在一个文件中。通常的约定是每个文件一个类,因此您可能希望将每个类分解为自己的文件。
结果
修改强>
@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;将再次为该行中的每一行开火)案件)。如果可以在您的方案中修改单个行,那该怎么办?
PropertyChanged
上监听,或者您可以使用DataGrid的CellEditEnding
事件。CollectionChanged
事件,或使用DataGrid的AddingNewItem
/ UnloadingRow
事件。您只需触发重新计算与修改行相邻的行的适当边框。