none
WPF, DataGrid, DataGridTemplateColumn: динамически создаваемые колонки и привязка к данным внутри их шаблонов. RRS feed

  • Вопрос

  • Помогите разобраться с колонками типа DataGridTemplateColumn и привязкой к данным внутри элементов их шаблонов, если такие колонки создаются динамически и их имена заранее не известны. Кроме того, одни и те же шаблоны могут использоваться одновременно для разных колонок...

    Пример - загрузка в DataGrid файла Excel. Количество колонок заранее неизвестно. В своей программе я читаю файл в DataTable, представление которого (.DefaultView) позже присваивается ItemsSource грида. И вот я хочу произвольную колонку заменить на колонку типа DataGridTemplateColumn. Допустим сделаю, но как быть с привязками - в объектах (в отладчике) я нигде не смог найти как к ним подступиться.

    Ну и второй вопрос - допустим я сделаю заранее шаблон для отображения содержимого ячейки в ресурсах, но если я хочу использовать этот шаблон в нескольких колонках... как быть с привязками? Получается, что свойства CellTemplate для разных колонок грида будут указывать на один шаблон, где менять привязку к данным, в каких объектах?

    Для начала пример такой:

            <DataTemplate x:Key="CellTemplate">
                <Label Name="lblCellValue" Content="{Binding Path=column_0}"></Label>
            </DataTemplate>

    Данный шаблон привязывает контент метки к полю column_0 объекта DataRowView (вроде бы). Но если имя не известно, как изменить привязку?

    И второе, если этот шаблон я использую более, чем в одной колонке, то опять же, где и что менять, чтобы данный шаблон работал одновременно на нужные колонки? Уже проверил... метода Copy() объект DataTemplate не имеет :(.

    4 апреля 2017 г. 15:25

Ответы

  • Вопрос решен. Итак, чтобы динамически заменить текстовую колонку на колонку другого типа (я взял стандартную DataGridComboBox, не стал возиться с TemplateColumn) необходимо:

    1. Получить индекс заменяемой колонки (если колонку добавляем, то этот шаг не нужен). И саму колонку соответственно.

    2. Запомнить этот индекс и выполнить: dg.Columns.Remove(column или dg.Columns[Ind]);

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

    DataGridComboBoxColumn cCol = new DataGridComboBoxColumn();
    cCol.Header = "column_" + currColInd; // я заполняю Header, т.к. его имя в потом используется. Можно писать все что угодно - это текст заголовка.
    ...
    // далее я создаю источник для DropDownList колонки. В идеале - это ресурс или заранее созданный перечисляемый объект.
    List<string> ItemsList = new List<string>();
    foreach(GeologycalLayer Item in Enum.GetValues(typeof(GeologycalLayer)))
    {
    ItemsList.Add(Item.ToString());
    }
    cCol.ItemsSource = ItemsList; // Важно! Необходимо заполнить свойство ItemsSource колонки.

    // Создаем объект привязки к данным:
    Binding bd = new Binding();
    bd.Path = new PropertyPath("column_" + currColInd); // Важно! Это то самое, неизвестное заранее, имя свойства объекта, который представлен строка DataGrid.
    cCol.SelectedValueBinding = bd; // может быть SelectedItemBinding или TextBinding. Мне подошло SelectedValueBinding.

    // вставляем колонку в нужное место:

    dgData.Columns.Insert(currColInd, cCol);

    Все! Этих шагов достаточно, чтобы на лету добавить колонку, правда с TemplateColumn вопрос так и не решен. Как работать с привязками элементов управления в их шаблонах я не знаю. Если кто может объяснить, как подобное сделать для простой TemplateColumn, в которой есть CellTemptate с элементом ComboBox, который надо привязать к свойству объекта данных - тому огромное спасибо!

    • Помечено в качестве ответа Denis Prokofjev 6 апреля 2017 г. 13:38
    6 апреля 2017 г. 13:37

Все ответы

  • Попробую конретизировать еще.

    Так выглядит мой загруженный в DataGrid. Колонки грида создаются в момент присваивания свойству грида ItemsSource значения DataView. По умолчанию создаются DataGridTextColumn, однако, этот вид колонок имеет свойство HeaderTemplate, который я переопределяю сам. Дело в том, что данный шаблон вещь в себе, он содержит ComboBox, который заполнен одинаковыми значениями, и в нем нет никаких привязок к данным. поэтому я просто перебираю колонки и присваиваю этот шаблон и все работает.

    Предположим, что при выборе пункта "Описание геологического разреза..." необходимо, чтобы колонка заменялась на DataGridTemplateColumn со своим CellTemplate. И этот шаблон должен содержать ComboBox с фиксированным набором значений. Но самое сложное - я не понимаю просто, как мне создать правильные привязки? Колонки реального источника данных называются "column_0", "column_1" и т.д. Но неизвестно, на какую колонку придется выбор этого пункта - я заранее не знаю имени, к которому будет привязан шаблон.

    Хотя бы натолкните на идею, где посмотреть. Читаю про связывания данных (Общие сведения о связывании данных), но такого примера не нашел еще там. Если я правильно понял, то в момент создания колонки по шаблону создается набор элементов управления для ячейки, хотя бы в каком событии это происходит? Может есть смысл вручную создавать привязки для каждой ячейки в этом событии, если такое имеется? Помогите разобраться, не врублюсь никак в эту логику :)


    5 апреля 2017 г. 13:01
  • Добрый день.

    В свое время решая похожую задачу пришли к следующей схеме (применяется паттерн MVVM):

    1. Во VM есть коллекция столбцов с информацией о типе шаблона, пути биндинга, заголовке и т.д.

    2. Есть UserControl, который проверяет что за VM лижит у него в DataContext, ну и биндиться к его коллекции с информацией по столбцам.

    3. В коде контрола идет подписка на получение коллекции столбцов и изменении этой коллекции.

    4. При наступлении события из пункта 3, имеющиеся столбцы удаляются, и по полученной информации создаются новые.

    Если у вас не VM, то вы можете при присвоении коллекции сразу и столбцы из кода настраивать. Других вариантов на тот момент не нашли, а т.к. пользуемся им уже лет 8, то и искать лень.

    6 апреля 2017 г. 5:46
    Отвечающий
  • Алексей, спасибо. У меня скорее всего не MVVM, я пользуюсь простыми контролами и банальными событиями Click или SelectionChanged. Я вчера попробовал - я могу динамически удалить колонку в гриде и создать новую, привязать к ней шаблон. Но я не умею в этом случае строить привязки - не пойму где это надо делать.

    Вот пример кода (header - объект типа DataGridColumnHeader я его нахожу через Visual Tree и контрол, в котором выбрали новое значение):

    DataGridColumn currCol = header.Column;
    int currColInd = dgData.Columns.IndexOf(currCol); // на это место мы должны вставить колонку.
    //
    dgData.Columns.Remove(currCol); // удаление текущей колонки.

    // создание новой:
    DataGridTemplateColumn tCol = new DataGridTemplateColumn();
    tCol.Header = "column_" + currColInd;
    tCol.HeaderTemplate = Resources["GridColumn"] as DataTemplate;
    tCol.CellTemplate = Resources["CellTenplate"] as DataTemplate;
    tCol.CellEditingTemplate = Resources["EditCellTemplate"] as DataTemplate;

    dgData.Columns.Insert(currColInd, tCol);

    Колонка создается, шаблоны привязываются... но мне нужно теперь сделать Binding для новой колонки на свойство объекта коллекции с именем "column_" + currColInd. У меня так называются колонки в коллекции. Как видно из примера, заранее неизвестно, в какой колонке произойдет выбор.

    6 апреля 2017 г. 7:54
  • Вопрос решен. Итак, чтобы динамически заменить текстовую колонку на колонку другого типа (я взял стандартную DataGridComboBox, не стал возиться с TemplateColumn) необходимо:

    1. Получить индекс заменяемой колонки (если колонку добавляем, то этот шаг не нужен). И саму колонку соответственно.

    2. Запомнить этот индекс и выполнить: dg.Columns.Remove(column или dg.Columns[Ind]);

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

    DataGridComboBoxColumn cCol = new DataGridComboBoxColumn();
    cCol.Header = "column_" + currColInd; // я заполняю Header, т.к. его имя в потом используется. Можно писать все что угодно - это текст заголовка.
    ...
    // далее я создаю источник для DropDownList колонки. В идеале - это ресурс или заранее созданный перечисляемый объект.
    List<string> ItemsList = new List<string>();
    foreach(GeologycalLayer Item in Enum.GetValues(typeof(GeologycalLayer)))
    {
    ItemsList.Add(Item.ToString());
    }
    cCol.ItemsSource = ItemsList; // Важно! Необходимо заполнить свойство ItemsSource колонки.

    // Создаем объект привязки к данным:
    Binding bd = new Binding();
    bd.Path = new PropertyPath("column_" + currColInd); // Важно! Это то самое, неизвестное заранее, имя свойства объекта, который представлен строка DataGrid.
    cCol.SelectedValueBinding = bd; // может быть SelectedItemBinding или TextBinding. Мне подошло SelectedValueBinding.

    // вставляем колонку в нужное место:

    dgData.Columns.Insert(currColInd, cCol);

    Все! Этих шагов достаточно, чтобы на лету добавить колонку, правда с TemplateColumn вопрос так и не решен. Как работать с привязками элементов управления в их шаблонах я не знаю. Если кто может объяснить, как подобное сделать для простой TemplateColumn, в которой есть CellTemptate с элементом ComboBox, который надо привязать к свойству объекта данных - тому огромное спасибо!

    • Помечено в качестве ответа Denis Prokofjev 6 апреля 2017 г. 13:38
    6 апреля 2017 г. 13:37