Отображение трехмерных данных в WPF DataGrid

Я пытаюсь отобразить трехмерные данные в WPF DataGrid через DataTable привязку. Поскольку DataGrid имеет только строки и столбцы (2D), моя идея состоит в том, чтобы создать шаблон ячеек так, чтобы в них размещался ListView (или какой-либо другой элемент управления), и отображать значения третьего измерения, расположенные вертикально.

Желаемый результат таков:

Вот сильно упрощенный, но воспроизводимый код с фиктивными данными, моя попытка создать такой DataTable:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        ViewModel vm = new();

        MyDataGrid.DataContext = vm.Table.DefaultView;
        MyDataGrid.IsReadOnly = true;
    }

    public class ViewModel
    {
        public DataTable Table { get; init; }

        public ViewModel()
        {
            Table = new DataTable();

            Table.Columns.Add(" ");
            Table.Columns.Add("c1");
            Table.Columns.Add("c2");
            Table.Columns.Add("c3");

            for (int i = 1; i <= 3; i++ )
            {
                DataRow row = Table.NewRow();

                row[0] = new List<object>() { "r" + i } ;
                row[1] = new List<object>() { 10 * i, 20 * i, 25 * i } ;
                row[2] = new List<object>() { 12 * i, 26 * i, 30 * i } ;
                row[3] = new List<object>() { 16 * i, 28 * i, 36 * i };

                Table.Rows.Add(row);
            }
        }
    }
}

Как видите, я присваиваю List каждой ячейке, идея в том, что это создает третье измерение, сохраняющее несколько значений в одной ячейке.

Проблема в том, что я не знаю, как создать шаблон DataGrid в разметке WPF, чтобы это третье измерение правильно распаковывалось и отображалось, как на изображении, которое я показал выше. Я попробовал это:

<DataGrid x:Name = "MyDataGrid" ItemsSource = "{Binding}">
    <DataGrid.Resources>
        <DataTemplate x:Key = "DataGridTemplate">
            <ListView ItemsSource = "{Binding}">
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <TextBlock Text = "{Binding}"/>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
        </DataTemplate>
    </DataGrid.Resources>

    <DataGrid.CellStyle>
        <Style TargetType = "DataGridCell">
            <Setter Property = "ContentTemplate" Value = "{StaticResource DataGridTemplate}"/>
        </Style>
    </DataGrid.CellStyle>
</DataGrid>

Но когда я его компилирую и запускаю, DataGrid просто показывает пустые ячейки, хотя ошибок привязки нет. Если я не использую какой-либо шаблон, в ячейках отображается System.Data.DataRowView System.Collections.Generic.List1[System.Object]`.

Я был бы очень признателен, если бы кто-нибудь мог взглянуть (код, который я привел выше, является полностью воспроизводимым примером) и сообщить мне, как создать правильный шаблон? Или, если я поступаю неправильно, я открыт и для других предложений.

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

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
0
85
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Есть несколько проблем с кодом в том виде, в каком он у вас есть.

1. Добавьте Type для сложных DataTable столбцов.

Проблема 1 заключается в том, что ваши столбцы не типизированы, и это, похоже, приводит к тому, что DataRow["c1"] и т. д. отображается как значение string типа System.Collections.Generic.List1[System.Int32], а не сама коллекция. Смотрите скриншот отладчика ниже:

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

// row headers
Table.Columns.Add("c0", typeof(string)); 
// three-dimensional data
Table.Columns.Add("c1", typeof(IList));
Table.Columns.Add("c2", typeof(IList));
Table.Columns.Add("c3", typeof(IList));

2. Используйте DataGridTemplateColumn, чтобы построить сложные DataGrid столбцы.

Вторая проблема заключается в том, что я не думаю, что вы сможете добиться того, чего хотите, с помощью автоматически создаваемых столбцов. Если я изменю ваш CellTemplate на просто привязку к TextBlock, вы увидите, что контекст данных для этих ячеек на самом деле представляет собой весь объект DataRowView, а не только привязанный столбец:

<DataGrid.Resources>
    <DataTemplate x:Key = "DataGridTemplate">
        <TextBlock Text = "{Binding}" />
    </DataTemplate>
</DataGrid.Resources>

Обновив свой DataGrid, как показано ниже, я смог создать то, что, как мне кажется, вы ищете.

<DataGrid x:Name = "MyDataGrid"
            ItemsSource = "{Binding}"
            AutoGenerateColumns = "False">
    <DataGrid.Columns>
        <DataGridTextColumn Binding = "{Binding c0}" />
        <DataGridTemplateColumn Header = "c1">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <ListView ItemsSource = "{Binding c1}" />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
        <DataGridTemplateColumn Header = "c2">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <ListView ItemsSource = "{Binding c2}" />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
        <DataGridTemplateColumn Header = "c3">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <ListView ItemsSource = "{Binding c3}" />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Columns>
</DataGrid>

Результат

ОБНОВЛЕНИЕ: 3. Если требуется автоматическое создание столбцов...

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

Как настроить автоматически создаваемые столбцы в элементе управления DataGridhttps://stackoverflow.com/a/1755556/15534202

XAML: добавление обработчика событий автоматического создания

<DataGrid x:Name = "MyDataGrid"
            ItemsSource = "{Binding}"
            AutoGenerateColumns = "True"
            AutoGeneratingColumn = "MyDataGrid_AutoGeneratingColumn">
</DataGrid>

Программный код: переопределить генерацию столбцов List

private void MyDataGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
    // Optional: Remove the column header for the row headers
    if (e.Column.Header == "c0") e.Column.Header = "";

    // Let the non-list data auto-generate normally
    if (!e.PropertyType.IsAssignableFrom(typeof(IList))) return;

    // create a new column
    var newColumn = new DataGridTemplateColumn();
    newColumn.Header = e.Column.Header;
            
    // create a new binding
    var binding = new Binding(e.PropertyName);
    binding.Mode = BindingMode.TwoWay;

    // setup the cell template
    var elementFactory = new FrameworkElementFactory(typeof(ListBox));
    elementFactory.SetValue(ListBox.ItemsSourceProperty, binding);
    var newCellTemplate = new DataTemplate();
    newCellTemplate.VisualTree = elementFactory;
    newColumn.CellTemplate = newCellTemplate;
            
    // assign the new column
    e.Column = newColumn;
}

Спасибо большое за ответ, очень подробно и информативно. Проблема в том, что я не знаю, сколько будет столбцов и строк и каковы будут имена их заголовков. Поэтому их нельзя явно определить в разметке WPF, их необходимо генерировать из DataTable. Извините, если я не пояснил это в своем посте. Не могли бы вы обновить свой ответ, чтобы учесть это? Я отредактирую свой пост и разъясню это другим людям.

J R 11.03.2024 21:57

Попался. Думаю, у меня есть идея. Я опубликую обновление в ближайшее время.

egeer 11.03.2024 22:55

@JR, я добавил опцию автоматического создания столбцов. Он не использует XAML для создания шаблона, а вместо этого использует код программной части, но я не знаю, есть ли способ обойти это. Дайте мне знать, если это подходит для ваших нужд.

egeer 11.03.2024 23:53

Большое спасибо, этот третий вариант кажется тем, что мне нужно. Я просто не могу понять, почему отображается пустой первый столбец (где должны быть заголовки строк: «r1», «r2» и «r3»?

J R 12.03.2024 20:20

Вы обязательно включили проверку типа столбца? ` if (!e.PropertyType.IsAssignableFrom(typeof(IList))) return;`

egeer 12.03.2024 20:22

Я почти уверен, что столбец строки заголовка правильно отображался в моем прототипе, но это на другом компьютере.

egeer 12.03.2024 20:23

Да, я так считаю. Это мой код C#: ссылка , а вот разметка WPF: ссылка

J R 12.03.2024 20:25

Первый столбец не должен иметь тип IList: Table.Columns.Add(" ", typeof(IList));

egeer 12.03.2024 20:25

Table.Columns.Add(" ") // только первый столбец

egeer 12.03.2024 20:25

Да, я попробовал это, но потом в первом столбце в каждой ячейке просто отображается «System.Data.DataRowView».

J R 12.03.2024 20:38

Интересно, что если я напишу Table.Columns.Add("c0", typeof(IList)) вместо Table.Columns.Add("", typeof(IList)), это начнет работать. Не знаю почему, я бы предпочел, чтобы заголовок первого столбца был пустым.

J R 12.03.2024 20:40

Это странно... Я думаю, есть что-то в том, как работает автогенерация столбцов, если вы не указываете имя. Это работает так, как я думаю, вы этого хотите Pastebin.com/UvmpVVWE

egeer 12.03.2024 21:12

Если это сработает для вас, я обновлю свой ответ

egeer 12.03.2024 21:12

Это немного похоже на хак, но да, это работает. Не стесняйтесь обновлять ответ, я отмечу его как правильный. Мне только сейчас интересно, возможно, выбор DataGrid был неправильным поворотом, если он требует такого ручного создания столбцов и, похоже, не предоставляет никаких диагностических инструментов, чтобы понять, почему он не работает, когда что-то идет не так. Может быть, мне следовало вместо этого использовать ListView/GridView? В любом случае большое спасибо за ответ, он исключительно качественный. Я бы проголосовал дважды, если бы мог.

J R 13.03.2024 04:42

@JR, я обновил ответ, так что примите его, если он вам помог. Я рад, что это помогло!

egeer 13.03.2024 16:05

Другие вопросы по теме