none
WPF. DataGridTemplateColumn: как получить доступ к элементам заголовка (Label, ComboBox...) такой колонки? Например через объект колонки DataGrid. RRS feed

  • Вопрос

  • Задача следующая: есть на входе файл MSExcel со своим набором колонок. На выходе должны получить файл Excel только с колонками в строгой последовательности (по шаблону) и с заголовком нужного формата. Сопоставление колонок файла колонкам шаблона выполняется пользователем интерактивно. Т.е. пользователь вручную их сопоставляет.

    Я решил поработать с DataGrid. Идея - загрузить файл Excel в таблицу, а заголовки таблицы сделать по шаблону, который бы включал элемент ComboBox с вариантами шаблонных колонок. Сложную колонку создаю как DataGridTemplateColumn, далее с помощью Grid компоную в две строки Label и ComboBox. К Combo можно зарегистрировать обработчик изменения значения SelectionChanged и отслеживать изменения конкретного элемента. Но даже последнее не так важно.

    Не могу вот чего понять...

    1. как мне через колонку DataGrid получить значение выбранного элемента в ее ComboBox? Т.е. пользователь сопоставил все колонки, нажал кнопку выгрузки и.. предполагаю такой алгоритм: перебор по строкам, перебор по колонкам - здесь получаем колонку для текущей ячейки и из заголовка колонки тащим идентификатор или индекс колонки шаблона. И тут загвоздка... как это сделать?

    Максимум, что я смог: представить колонку как DataGridTemplateColumn, в ней .HeaderTemplate в нем .Template.. и все. Как найти элемент ComboBox не понимаю, во всех свойствах чего-то ожидаемого не могу найти. Недавно работаю с WPF, знаю, что существует модель зависимостей, что шаблоны отвечают за визуализацию данных, но как это использовать реально не понимаю.

    2. Второе - шаблон колонки (тип DataTemplate) я планирую использовать многократно и следовательно, мне придется менять текст во втором элементе - Label, чтобы колонки не выглядели одинаково. Но по сути этот тот же вопрос - через колонку DataGrid получить доступ к свойствам элементов ее заголовка.

    29 марта 2017 г. 12:06

Ответы

  • Итак, окончательное решение. Поскольку я новичок в WPF и для меня все имеет значение, писать буду подробно.

    1. Фреймворк содержит класс VisualTreeHelper, который находится в пространстве имен System.Windows.Media. Данный класс содержит процедуры, необходимые для связки объектов CLR с визуальными объектами (поправьте, если ошибаюсь).

    2. Похожих вопросов, как и ожидалось море, и вот еще одна тема, ответ на которую я использовал в своей программе: How to access datagrid template column textbox text WPF C# Я использовал оттуда функцию FindChild<T>() полностью и на ее основе написал свою, которая ищет элемент DataGridColumnHeader для конкретной колонки таблицы. Моя функция выглядит так:

    private DataGridColumnHeader FindColumnHeader(DependencyObject parent, DataGridColumn column)
            {
                if (parent == null)
                    return null;

                DataGridColumnHeader foundChild = null;

                int childrenCount = VisualTreeHelper.GetChildrenCount(parent);

                for (int i = 0; i < childrenCount; i++)
                {
                    DependencyObject child = VisualTreeHelper.GetChild(parent, i);
                    //
                    if (child as DataGridColumnHeader == null)
                    {
                        foundChild = FindColumnHeader(child, column);
                        //
                        if (foundChild != null)
                            break;
                    }
                    else
                    {
                        if (column != null)
                        {
                            //FrameworkElement elem = child as FrameworkElement;
                            //
                            if ((child as DataGridColumnHeader).Column == column)
                            {
                                foundChild = child as DataGridColumnHeader;
                                break;
                            }
                            else
                            {
                                foundChild = FindColumnHeader(child, column);
                                //
                                if (foundChild != null)
                                    break;
                            }
                        }
                        else
                        {
                            foundChild = (DataGridColumnHeader)child;
                            break;
                        }
                    }
                }

                return foundChild;
            }

    3. Далее небольшой тестовый пример, в котором шаблон заголовка выглядит так:

    <DataGridTemplateColumn>
                        <DataGridTemplateColumn.HeaderTemplate>
                            <DataTemplate>
                                <Grid>
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="100"></ColumnDefinition>
                                    </Grid.ColumnDefinitions>
                                    <Grid.RowDefinitions>
                                        <RowDefinition Height="25"></RowDefinition>
                                        <RowDefinition Height="25"></RowDefinition>
                                    </Grid.RowDefinitions>
                                    <Label Name="ColumnText" Grid.Row="0">123</Label>
                                    <ComboBox Grid.Row="1" Name="ColumnCombo" SelectionChanged="ComboBox_SelectionChanged">
                                        <TextBlock>LG Nexus 5X</TextBlock>
                                        <TextBlock>Huawai Nexus 6P</TextBlock>
                                        <TextBlock>iPhone 6S</TextBlock>
                                        <TextBlock>iPhone 6S Plus</TextBlock>
                                        <TextBlock>Microsoft Lumia 950</TextBlock>
                                    </ComboBox>
                                </Grid>
                            </DataTemplate>
                        </DataGridTemplateColumn.HeaderTemplate>
                    </DataGridTemplateColumn>

    Сам пример устанавливает текст метки значением из ComboBox. Вызывается при изменении любого из ComboBox в заголовках таблицы:

    foreach (DataGridColumn column in MyGrid.Columns)
                {
                    DataGridColumnHeader header = FindColumnHeader(MyGrid, column);
                    Label lbl = null;
                    ComboBox combo = null;
                    if (header != null)
                    {
                        lbl = FindChild<Label>(header, "ColumnText");
                        //
                        combo = FindChild<ComboBox>(header, "ColumnCombo");
                        if (combo != null && lbl != null)
                            lbl.Content = combo.SelectedValue;
                    }
                }


    • Помечено в качестве ответа Denis Prokofjev 30 марта 2017 г. 8:27
    • Изменено Denis Prokofjev 30 марта 2017 г. 8:36
    30 марта 2017 г. 8:26
  • Вроде такая цепочка работает (используем пространство имен LinqToVisualTree из статьи, указанной выше):

    1) MyGrid.Descendants<DataGridColumnHeader>() - получаем всех потомков в дереве визуальных элементов (или как правильно его называть?) с типом DataGridColumnHeader для нашей DataGrid.

    2) Перебираем эти объекты, приводя к типу System.Windows.Controls.Primitives.DataGridColumnHeader. После приведения у них будет доступно свойство Column.

    3) В свою очередь, можно определить положение колонки в таблице через MyGrid.Columns.IndexOf((ОбъектИзПункта2 as DataGridColumnHeader).Column)

    Не просто однако... Я завтра буду пробовать уже с конкретными примерами и, в случае успеха, выложу код.

    • Помечено в качестве ответа Denis Prokofjev 30 марта 2017 г. 8:27
    29 марта 2017 г. 15:06

Все ответы

  • Сейчас разбираюсь с пространством имен "Linq to Visual Tree" (статья http://blog.scottlogic.com/2010/03/04/linq-to-visual-tree.html). В ней описано кастомное пространство имен, которое работает с визуальным деревом WPF (там оно называется Visual Tree) в формате Linq. Например, для окна или объекта DataGrid можно вызвать такой метод:

    IEnumerable combo = MyDataGrid.Descendants<ComboBox>(); - мы получим список всех потомков типа ComboBox в визуальном дереве. Я могу обратиться к любому из свойств, в том числе к имени и выбранному значению. Но!

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

    Мне бы хотелось, хотя бы, применить метод Descendant() к какому-то элементу, представляющему конкретную колонку, но я не могу найти такой элемент. Объекты из Columns не подходят, шаблоны в них - тоже.


    29 марта 2017 г. 13:34
  • Вроде такая цепочка работает (используем пространство имен LinqToVisualTree из статьи, указанной выше):

    1) MyGrid.Descendants<DataGridColumnHeader>() - получаем всех потомков в дереве визуальных элементов (или как правильно его называть?) с типом DataGridColumnHeader для нашей DataGrid.

    2) Перебираем эти объекты, приводя к типу System.Windows.Controls.Primitives.DataGridColumnHeader. После приведения у них будет доступно свойство Column.

    3) В свою очередь, можно определить положение колонки в таблице через MyGrid.Columns.IndexOf((ОбъектИзПункта2 as DataGridColumnHeader).Column)

    Не просто однако... Я завтра буду пробовать уже с конкретными примерами и, в случае успеха, выложу код.

    • Помечено в качестве ответа Denis Prokofjev 30 марта 2017 г. 8:27
    29 марта 2017 г. 15:06
  • Итак, окончательное решение. Поскольку я новичок в WPF и для меня все имеет значение, писать буду подробно.

    1. Фреймворк содержит класс VisualTreeHelper, который находится в пространстве имен System.Windows.Media. Данный класс содержит процедуры, необходимые для связки объектов CLR с визуальными объектами (поправьте, если ошибаюсь).

    2. Похожих вопросов, как и ожидалось море, и вот еще одна тема, ответ на которую я использовал в своей программе: How to access datagrid template column textbox text WPF C# Я использовал оттуда функцию FindChild<T>() полностью и на ее основе написал свою, которая ищет элемент DataGridColumnHeader для конкретной колонки таблицы. Моя функция выглядит так:

    private DataGridColumnHeader FindColumnHeader(DependencyObject parent, DataGridColumn column)
            {
                if (parent == null)
                    return null;

                DataGridColumnHeader foundChild = null;

                int childrenCount = VisualTreeHelper.GetChildrenCount(parent);

                for (int i = 0; i < childrenCount; i++)
                {
                    DependencyObject child = VisualTreeHelper.GetChild(parent, i);
                    //
                    if (child as DataGridColumnHeader == null)
                    {
                        foundChild = FindColumnHeader(child, column);
                        //
                        if (foundChild != null)
                            break;
                    }
                    else
                    {
                        if (column != null)
                        {
                            //FrameworkElement elem = child as FrameworkElement;
                            //
                            if ((child as DataGridColumnHeader).Column == column)
                            {
                                foundChild = child as DataGridColumnHeader;
                                break;
                            }
                            else
                            {
                                foundChild = FindColumnHeader(child, column);
                                //
                                if (foundChild != null)
                                    break;
                            }
                        }
                        else
                        {
                            foundChild = (DataGridColumnHeader)child;
                            break;
                        }
                    }
                }

                return foundChild;
            }

    3. Далее небольшой тестовый пример, в котором шаблон заголовка выглядит так:

    <DataGridTemplateColumn>
                        <DataGridTemplateColumn.HeaderTemplate>
                            <DataTemplate>
                                <Grid>
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="100"></ColumnDefinition>
                                    </Grid.ColumnDefinitions>
                                    <Grid.RowDefinitions>
                                        <RowDefinition Height="25"></RowDefinition>
                                        <RowDefinition Height="25"></RowDefinition>
                                    </Grid.RowDefinitions>
                                    <Label Name="ColumnText" Grid.Row="0">123</Label>
                                    <ComboBox Grid.Row="1" Name="ColumnCombo" SelectionChanged="ComboBox_SelectionChanged">
                                        <TextBlock>LG Nexus 5X</TextBlock>
                                        <TextBlock>Huawai Nexus 6P</TextBlock>
                                        <TextBlock>iPhone 6S</TextBlock>
                                        <TextBlock>iPhone 6S Plus</TextBlock>
                                        <TextBlock>Microsoft Lumia 950</TextBlock>
                                    </ComboBox>
                                </Grid>
                            </DataTemplate>
                        </DataGridTemplateColumn.HeaderTemplate>
                    </DataGridTemplateColumn>

    Сам пример устанавливает текст метки значением из ComboBox. Вызывается при изменении любого из ComboBox в заголовках таблицы:

    foreach (DataGridColumn column in MyGrid.Columns)
                {
                    DataGridColumnHeader header = FindColumnHeader(MyGrid, column);
                    Label lbl = null;
                    ComboBox combo = null;
                    if (header != null)
                    {
                        lbl = FindChild<Label>(header, "ColumnText");
                        //
                        combo = FindChild<ComboBox>(header, "ColumnCombo");
                        if (combo != null && lbl != null)
                            lbl.Content = combo.SelectedValue;
                    }
                }


    • Помечено в качестве ответа Denis Prokofjev 30 марта 2017 г. 8:27
    • Изменено Denis Prokofjev 30 марта 2017 г. 8:36
    30 марта 2017 г. 8:26