Заполнение Datagrid динамическими столбцами

У меня есть Datagrid, который должен быть заполнен динамически.

Вывод таблицы выглядит следующим образом:

id | image | name | Description | Name-1 | Name-N

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

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

Сейчас у меня есть Listbox whitch содержит имена динамически созданных столбцов и метод, который заполняет статические столбцы. Я также могу загрузить данные для каждого пользователя. теперь мне нужно объединить их в одну большую таблицу.

Основная проблема заключается в следующем: как поместить "Userdata" и статический контент в один datagrid.

Ответ 1

Существует как минимум три способа сделать это:

  • Модифицировать столбцы DataGrid вручную из кода
  • Используйте DataTable в качестве источника Items *
  • Используйте CustomTypeDescriptor

    * рекомендуется для простоты


1-й подход: используйте код для создания столбцов DataGrid во время выполнения. Это просто реализовать, но, может быть, это немного хаки, особенно если вы используете MVVM. Таким образом, у вас будет DataGrid с фиксированными столбцами:

<DataGrid x:Name="grid">
    <DataGrid.Columns>
        <DataGridTextColumn Binding="{Binding id}" Header="id" />
        <DataGridTextColumn Binding="{Binding image}" Header="image" />
    </DataGrid.Columns>
</DataGrid>

Когда у вас есть готовые "Имена", измените сетку, добавив/удалив столбцы, например:

// add new columns to the data grid
void AddColumns(string[] newColumnNames)
{
    foreach (string name in newColumnNames)
    {
        grid.Columns.Add(new DataGridTextColumn { 
            // bind to a dictionary property
            Binding = new Binding("Custom[" + name + "]"), 
            Header = name 
        });
    }
}

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

public class CustomUser : User
{
    public Dictionary<string, object> Custom { get; set; }

    public CustomUser() : base()
    {
        Custom = new Dictionary<string, object>();
    }
}

Заполните ItemsSource коллекцией этого нового класса CustomUser:

void PopulateRows(User[] users, Dictionary<string, object>[] customProps)
{
    var customUsers = users.Select((user, index) => new CustomUser {
        Custom = customProps[index];
    });
    grid.ItemsSource = customUsers;
}

Таким образом, связывая его, например:

var newColumnNames = new string[] { "Name1", "Name2" };
var users = new User[] { new User { id="First User" } };
var newProps = new Dictionary<string, object>[] {
    new Dictionary<string, object> { 
        "Name1", "First Name of First User",
        "Name2", "Second Name of First User",
    },
};
AddColumns(newColumnNames);
PopulateRows(users, newProps);

Второй подход: используйте DataTable. Это использует инфраструктуру настраиваемого типа под капотом, но ее проще в использовании. Просто привяжите свойство DataGrid ItemsSource к свойству DataTable.DefaultView:

<DataGrid ItemsSource="{Binding Data.DefaultView}" AutoGenerateColumns="True" />

Затем вы можете определить столбцы, как вам нравится, например:

Data = new DataTable();

// create "fixed" columns
Data.Columns.Add("id");
Data.Columns.Add("image");

// create custom columns
Data.Columns.Add("Name1");
Data.Columns.Add("Name2");

// add one row as an object array
Data.Rows.Add(new object[] { 123, "image.png", "Foo", "Bar" });

Третий подход: использовать расширяемость системы типа .Net. В частности, используйте CustomTypeDescriptor. Это позволяет создать пользовательский тип во время выполнения; который, в свою очередь, позволяет вам сообщить DataGrid, что ваш тип имеет свойства "Name1", "Name2",... "NameN" или любые другие, которые вы хотите. См. здесь для простого примера этого подхода.

Ответ 2

Второй подход: используйте DataTable. Это использует инфраструктуру настраиваемого типа под капотом, но ее проще в использовании. Просто привяжите объект ItemsGrid ItemsSource к свойству DataTable.DefaultView:

Это почти сработало, но вместо привязки к свойству свойства DataTable.DefaultView я создал свойство типа DataView и привязано к нему.

<DataGrid ItemsSource="{Binding DataView, Mode=TwoWay}" AutoGenerateColumns="True" />

Ответ 3

Если вам не требуется показывать это в одну большую таблицу DataGrid (таблица), вы можете иметь DataGrid с идентификатором, изображением, именем, описанием и когда один из этих записей выбирается на этом DataGrid, тогда вы показываете/обновляете ListBox с именем изображений, относящихся к этой выбранной записи.