我写了一个简单的示例应用程序,只有一个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
基本上将“更改检测”添加到您的应用程序,允许您的绑定自动重新绑定到新的/更改的数据。最后一点是我用LINQ query来照顾分组逻辑。此特定查询按Number
排序行,然后按Number
对它们进行分组。然后,它创建AttributeUpdateViewModel
实例,根据当前AttributeUpdate
是否与其组中的最后一项匹配来填充IsLastInGroup
。
注:为了简单起见,我在一个文件中放了几个类。通常的约定是每个文件一个类,所以你可能想要将每个类分成它自己的文件。
结果
编辑
@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();
当使用标准的'DataGrid'时没有像这样合并行的直接方式。有一些第三方网格可能支持这种事情,但如果您尝试一起破解解决方案,如果您开始让用户应用排序,分组或过滤,将会变成一场噩梦。 – 2014-10-29 20:55:32
@MikeStrobel:但我不想合并行。我只想在前面和后面的记录上格式化它们 – 2014-10-29 20:57:16
它们是否实际上“合并”并不是真正的重点。你想根据相邻行是否共享数据来有条件地格式化行,这将比你想象的要复杂得多。行可以基于排序和过滤被添加,移除和重新排列,更不用说为行虚拟化而回收。 – 2014-10-29 20:59:45