我寫了一個簡單的示例應用程序,只有一個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