Форматировать 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) {
...
}
Но это может быть только форматирование по данным этой самой строки, и я не знаю, какая строка идет после или до. Поэтому я не могу решить, как отформатировать границу этой строки.
Как я могу отформатировать строку в зависимости Об информации не только текущей строки, но и предыдущих и последующих строк?
2 ответа:
Я написал простой пример приложения, в котором есть только файл XAML и код за ним. Чтобы воссоздать то, что я сделал, просто создайте новое приложение WPF 4.5 и вставьте приведенный ниже код в соответствующие файлы.
Мое решение использует модели представлений, которые позволяют вам делать все с привязками данных (и не требуют, чтобы вы связывали события в коде).
Это может показаться гораздо большим количеством кода, чем вы ожидали, но имейте в виду, что это полный пример, и его много это просто подстава. Что касается кода, который действительно имеет значение, надеюсь, вы обнаружите, что, хотя он добавляет приличное количество строк, он дает вам очень мощный шаблон для создания всевозможных классных пользовательских интерфейсов в WPF. Я добавил некоторые комментарии после каждого файла кода, чтобы, надеюсь, сделать его немного легче понять, что делает код.
Главное окно.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>
Это в основном простая таблица данных с текстовыми столбцами. Магия-это заказная строка. стиль, который создает горизонтальные линии сетки по мере необходимости. (Подробнее о привязках данных см. ниже.)
Главное окно.код 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 вокруг.{Binding name_of_property}
в файле XAML, просто подключаются к данным и информации о форматировании в моделях представления. Если базовые данные могут изменяться во время работы приложения, вам потребуется, чтобы ваши модели представления реализовывали интерфейс INotifyPropertyChanged.INotifyPropertyChanged
существенно добавляет "обнаружение изменений" в ваше приложение, позволяя привязкам автоматическая повторная привязка к новым / измененным данным.Наконец, я использовал запросLINQ , чтобы позаботиться о логике группировки. Этот конкретный запрос сортирует строки по
Примечание: чтобы упростить задачу, я поместил несколько классов в один файл. Обычное соглашение - один класс на файл, поэтому вы можете захотеть вырваться каждый класс в свой собственный файл.Number
, а затем группирует их поNumber
. Затем он создает экземплярыAttributeUpdateViewModel
, заполняяIsLastInGroup
на основе того, соответствует ли текущийAttributeUpdate
последнему элементу в своей группе.Результат
Edit
Комментарий @ Mike Strobel указывает на то, что сортировка по номеру может быть не обязательно желательной. Например, пользователь может захотеть выполнить сортировку по другому столбцу, но все равно видеть строки, сгруппированные по номерам. Я не уверен, что это будет обычным случаем использования, но если это требование, вы можете просто заменить в другом запросе LINQ, который сравнивает "текущие" значения с " next" значения, затем определяет, изменяется ли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();
Если вы просто хотите скрыть нижнюю границу для строк, за которыми следует строка с тем же идентификатором, то почему бы просто не сравнить текущую модель строки со следующей моделью строки?
Вышесказанное должно работать нормально, если коллекция фиксирована (т. е. отдельные элементы не добавляются или удаляются), даже если она переупорядочена (так как "LoadingRow" будет срабатывать снова для каждой строки в этом случае). Как насчет того, что отдельные строки могут быть изменены в вашем сценарии?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 } }
- если" число " изменено: в идеале прислушайтесь к этому на модели представления
PropertyChanged
, или вы можете использовать событие DataGridCellEditEnding
.- при добавлении или удалении отдельных строк: прослушайте событие
CollectionChanged
в базовой коллекции или используйте функцию DataGrid.AddingNewItem
/UnloadingRow
события.Вам просто нужно будет вызвать повторное вычисление соответствующей границы для строк, соседних с измененной строкой.