Захват ссылки на элемент внутри DataGridTemplateColumn

Я уже некоторое время борюсь с этой проблемой и не могу найти на нее ответов. Я смоделировал простой файл XAML, чтобы продемонстрировать проблему (я не предоставляю свой реальный файл XAML, поскольку в нем больше, чем нужно для этого вопроса).

Вот мой вопрос: учитывая следующий файл XAML, как я могу получить ссылку в моем программном коде на selectHeight ComboBox, который находится в DataGridTemplateColumn.CellEditingTemplate DataTemplate? Мне нужна ссылка, чтобы я мог изменить свойство ComboBox на основе выбора в selectAge ComboBox.

<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
    <DataGrid Name="grdPeople"
                    AutoGenerateColumns="False"  
                    AlternatingRowBackground="LightBlue" 
                    CanUserAddRows="True" CanUserDeleteRows="True" IsEnabled="True"
                    MaxHeight="400" VerticalScrollBarVisibility="Auto">

        <DataGrid.Columns>
            <!--Name Column-->
            <DataGridTemplateColumn Header="MyName" CanUserReorder="False" CanUserResize="False"  CanUserSort="False" >
                <DataGridTemplateColumn.CellTemplate >
                    <DataTemplate>
                        <Label Content="{Binding Path=Name}"/>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
                <DataGridTemplateColumn.CellEditingTemplate >
                    <DataTemplate>
                        <TextBox Text="{Binding Path=Name}"/>
                    </DataTemplate>
                </DataGridTemplateColumn.CellEditingTemplate>
            </DataGridTemplateColumn>

            <!--Age Column-->
            <DataGridTemplateColumn Header="MyAge">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <Label Content="{Binding Path=Age}"></Label>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
                <DataGridTemplateColumn.CellEditingTemplate>
                    <DataTemplate>
                        <ComboBox Name="selectAge" ItemsSource="{Binding Path=AgeOpts}">
                            <ComboBox.ItemTemplate>
                                <DataTemplate>
                                    <Label Content="{Binding Path=Age}"></Label>
                                </DataTemplate>
                            </ComboBox.ItemTemplate>    
                        </ComboBox>
                    </DataTemplate>
                </DataGridTemplateColumn.CellEditingTemplate>
            </DataGridTemplateColumn>

            <!--Height Column-->
            <DataGridTemplateColumn Header="MyHeight">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <Label Content="{Binding Path=Height}"></Label>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
                <DataGridTemplateColumn.CellEditingTemplate>
                    <DataTemplate>
                        <ComboBox Name="selectHeight" ItemsSource="{Binding Path=HeightOpts}">
                            <ComboBox.ItemTemplate>
                                <DataTemplate>
                                    <Label Content="{Binding Path=Height}"></Label>
                                </DataTemplate>
                            </ComboBox.ItemTemplate>
                        </ComboBox>
                    </DataTemplate>
                </DataGridTemplateColumn.CellEditingTemplate>
            </DataGridTemplateColumn>
        </DataGrid.Columns>
    </DataGrid>
</Grid>

I realize that what I'm trying to do may not be exactly "best practice" but I'm trying to work with what I've been given in legacy code.

Я нашел эту страницу MSDN, относящуюся к тому, что я хочу сделать, но не могу понять, как перевести пример в мой сценарий: http://msdn.microsoft.com/en-us/library/system.windows.frameworktemplate.findname.aspx.

Любая помощь будет оценена по достоинству! Спасибо!


person Tom    schedule 11.08.2011    source источник


Ответы (1)


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

Вы используете это так: ComboBox cbx = FindChild<ComboBox>(grdPeople, "selectHeight");

Он вернет null, если не сможет найти ComboBox с именем "selectHeight", поэтому обязательно проверьте, есть ли cbx == null перед его использованием.

public static T FindChild<T>(DependencyObject parent, string childName)
    where T : DependencyObject
{
    // Confirm parent and childName are valid. 
    if (parent == null) return null;

    T foundChild = null;

    int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
    for (int i = 0; i < childrenCount; i++)
    {
        var child = VisualTreeHelper.GetChild(parent, i);
        // If the child is not of the request child type child
        T childType = child as T;
        if (childType == null)
        {
            // recursively drill down the tree
            foundChild = FindChild<T>(child, childName);

            // If the child is found, break so we do not overwrite the found child. 
            if (foundChild != null) break;
        }
        else if (!string.IsNullOrEmpty(childName))
        {
            var frameworkElement = child as FrameworkElement;
            // If the child's name is set for search
            if (frameworkElement != null && frameworkElement.Name == childName)
            {
                // if the child's name is of the request name
                foundChild = (T)child;
                break;
            }
            else
            {
                // recursively drill down the tree
                foundChild = FindChild<T>(child, childName);

                // If the child is found, break so we do not overwrite the found child. 
                if (foundChild != null) break;
            }
        }
        else
        {
            // child element found.
            foundChild = (T)child;
            break;
        }
    }

    return foundChild;
}

public static T FindChild<T>(DependencyObject parent)
    where T : DependencyObject
{
    // Confirm parent is valid. 
    if (parent == null) return null;

    T foundChild = null;

    int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
    for (int i = 0; i < childrenCount; i++)
    {
        var child = VisualTreeHelper.GetChild(parent, i);
        // If the child is not of the request child type child
        T childType = child as T;
        if (childType == null)
        {
            // recursively drill down the tree
            foundChild = FindChild<T>(child);

            // If the child is found, break so we do not overwrite the found child. 
            if (foundChild != null) break;
        }
        else
        {
            // child element found.
            foundChild = (T)child;
            break;
        }
    }
    return foundChild;
}
person Rachel    schedule 11.08.2011
comment
Спасибо за быстрый ответ. Я попробовал это, но не смог найти элемент управления в моем примере. Сообщение MSDN, на которое я ссылался, упоминало получение ContentPresenter элемента ListBox, чтобы вы могли получить установленный для него DataTemplate, а затем вызвать метод FindName для этого DataTemplate. Ваш метод, похоже, не учитывает DataTemplates, но, может быть, я что-то упускаю? - person Tom; 11.08.2011
comment
@Tom Когда ты пытаешься найти ComboBox? Видно ли это на экране, когда вы это делаете? - person Rachel; 11.08.2011
comment
Нет, так как он виден только тогда, когда ячейка находится в редактируемом режиме. Если вам будет проще, вы можете скачать мое заархивированное решение здесь: dropdo.com/7in/WpfTest. Это в VB.Net. Я перевел ваш метод на VB. Я пытаюсь получить ссылку на поле со списком selectHeight в обработчике событий selectAge_SelectionChanged в MainWindow.xaml.vb. - person Tom; 11.08.2011
comment
WPF выгружает невидимые элементы управления, поэтому, если ComboBox отсутствует на экране, значит, его нет в визуальном дереве. Однако ComboBox выглядит так, как будто он привязан к свойству Height, так почему бы вам просто не использовать его? - person Rachel; 11.08.2011
comment
Я не уверен, что вы имеете в виду, используя свойство Height? Я пытаюсь установить selectHeight ComboBox как включенный или отключенный на основе выбранного значения в selectAge ComboBox. Например, если кто-то выбирает возраст 30 лет, я хочу сделать поле со списком selectHeight недоступным. - person Tom; 11.08.2011
comment
Судя по вашему коду XAML, у вас есть класс, содержащий свойства для Name, Age и Height. Вы можете использовать код программной части, чтобы сказать что-то вроде if (((Person)heightComboBox.DataContext).Age > 30) heightComboBox.IsEnabled = false;, или вы можете сделать это в XAML, используя DataTrigger и Converter (рекомендуется) - person Rachel; 11.08.2011
comment
Все дело в том, что я не могу получить ссылку на selectHeight ComboBox в коде позади. Итак, то, что вы предлагаете, не будет работать, потому что для этого требуется ссылка на selectHeight ComboBox. - person Tom; 11.08.2011
comment
Добавьте код из комментария выше к событию Loaded вашего поля со списком selectHeight. ComboBox будет объектом sender, поэтому вам нужно будет привести его var heightComboBox = (ComboBox)sender; - person Rachel; 11.08.2011
comment
А, теперь я понимаю, что вы имеете в виду. Я думал об этом в контексте обработчика событий selectAge_SelectionChanged, в котором ссылка на selectHeight ComboBox была недоступна. Это работает для меня. - person Tom; 11.08.2011