Я пытаюсь отобразить трехмерные данные в 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 модифицируется или воссоздается.





Есть несколько проблем с кодом в том виде, в каком он у вас есть.
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));
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>
Если ваш вариант использования требует автоматического создания столбцов, как указано в комментарии, вы можете переопределить создание столбцов для этих конкретных столбцов. Этот подход немного менее понятен и основан на создании этих столбцов в коде программной части. В приведенном ниже примере ссылаются на эти две ссылки:
Как настроить автоматически создаваемые столбцы в элементе управления 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;
}
Попался. Думаю, у меня есть идея. Я опубликую обновление в ближайшее время.
@JR, я добавил опцию автоматического создания столбцов. Он не использует XAML для создания шаблона, а вместо этого использует код программной части, но я не знаю, есть ли способ обойти это. Дайте мне знать, если это подходит для ваших нужд.
Большое спасибо, этот третий вариант кажется тем, что мне нужно. Я просто не могу понять, почему отображается пустой первый столбец (где должны быть заголовки строк: «r1», «r2» и «r3»?
Вы обязательно включили проверку типа столбца? ` if (!e.PropertyType.IsAssignableFrom(typeof(IList))) return;`
Я почти уверен, что столбец строки заголовка правильно отображался в моем прототипе, но это на другом компьютере.
Да, я так считаю. Это мой код C#: ссылка , а вот разметка WPF: ссылка
Первый столбец не должен иметь тип IList: Table.Columns.Add(" ", typeof(IList));
Table.Columns.Add(" ") // только первый столбец
Да, я попробовал это, но потом в первом столбце в каждой ячейке просто отображается «System.Data.DataRowView».
Интересно, что если я напишу Table.Columns.Add("c0", typeof(IList)) вместо Table.Columns.Add("", typeof(IList)), это начнет работать. Не знаю почему, я бы предпочел, чтобы заголовок первого столбца был пустым.
Это странно... Я думаю, есть что-то в том, как работает автогенерация столбцов, если вы не указываете имя. Это работает так, как я думаю, вы этого хотите Pastebin.com/UvmpVVWE
Если это сработает для вас, я обновлю свой ответ
Это немного похоже на хак, но да, это работает. Не стесняйтесь обновлять ответ, я отмечу его как правильный. Мне только сейчас интересно, возможно, выбор DataGrid был неправильным поворотом, если он требует такого ручного создания столбцов и, похоже, не предоставляет никаких диагностических инструментов, чтобы понять, почему он не работает, когда что-то идет не так. Может быть, мне следовало вместо этого использовать ListView/GridView? В любом случае большое спасибо за ответ, он исключительно качественный. Я бы проголосовал дважды, если бы мог.
@JR, я обновил ответ, так что примите его, если он вам помог. Я рад, что это помогло!
Спасибо большое за ответ, очень подробно и информативно. Проблема в том, что я не знаю, сколько будет столбцов и строк и каковы будут имена их заголовков. Поэтому их нельзя явно определить в разметке WPF, их необходимо генерировать из DataTable. Извините, если я не пояснил это в своем посте. Не могли бы вы обновить свой ответ, чтобы учесть это? Я отредактирую свой пост и разъясню это другим людям.