Форматировать 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 2

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 последнему элементу в своей группе.

Примечание: чтобы упростить задачу, я поместил несколько классов в один файл. Обычное соглашение - один класс на файл, поэтому вы можете захотеть вырваться каждый класс в свой собственный файл.

Результат

DataGrid с линиями сетки между группами, а не между каждой строкой

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();

Если вы просто хотите скрыть нижнюю границу для строк, за которыми следует строка с тем же идентификатором, то почему бы просто не сравнить текущую модель строки со следующей моделью строки?

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.AddingNewItem / UnloadingRow события.

Вам просто нужно будет вызвать повторное вычисление соответствующей границы для строк, соседних с измененной строкой.