Как получить корневой элемент внутри примененного DataTemplate, если у него нет ключа ресурса?

Я хочу получить корневой элемент внутри применяемого DataTemplate. Я пробовал это, но у меня это не работает, потому что дляContentPresenter вернулся MyItemsControl.ItemContainerGenerator.ContainerFromItem(vm) где vm это ViewModel, ContentPresenter.ContentTemplate является null, хотя ContentPresenter.Content - соответствующие данные (та же ViewModel).

Я бы получил доступ DataTemplates как ресурсы, как здесь, но я не могу датьDataTemplates, потому что я хочу, чтобы они автоматически применялись ко всем элементам внутри ItemsControl. Поэтому я должен найти способ получитьDataTemplate из элемента внутри ItemsControl.

Я мог бы использовать if-else для определения DataTemplate ресурс в зависимости от vm.GetType() но хотелось бы без ItemContainerGenerator и, если возможно, в соответствии с шаблоном MVVM и без типов жесткого кодирования.

Ниже я думаю, что уместно в коде. Я использую, например,MyAudioFileSelector от MainWindow чтобы загрузить некоторые настройки из файла данных в пользовательский интерфейс, и я не уверен, как это сделать с помощью MVVM.

C# из моего реального проекта

(Я предполагаю, что в настоящее время есть только один AudioFileSelector и один ImageFileSelector, но в будущем, вероятно, у меня их будет больше.)

internal Control GetRootControlFromContentPresenter(ContentPresenter container)
{
    // what to put here?
    return null;
}
internal AudioFileSelector MyAudioFileSelector
{
    get
    {
        foreach (SettingDataVM vm in MyItemsControl.ItemsSource)
        {
            if (vm is AudioFileSettingDataVM)
            {
                return (AudioFileSelector)GetRootControlFromContentPresenter(
                    (ContentPresenter)MyItemsControl.ItemContainerGenerator.ContainerFromItem(vm));
            }
        }
        return null;
    }
}
internal ImageFileSelector MyImageFileSelector
{
    get
    {
        foreach (SettingDataVM vm in MyItemsControl.ItemsSource)
        {
            if (vm is ImageFileSettingDataVM)
            {
                return (ImageFileSelector)GetRootControlFromContentPresenter(
                    (ContentPresenter)MyItemsControl.ItemContainerGenerator.ContainerFromItem(vm));
            }
        }
        return null;
    }
}

Пример теста

XAML

<Window x:Class="wpf_test_6.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:wpf_test_6"
        mc:Ignorable="d"
        Title="MainWindow" Height="202" Width="274">
    <Window.Resources>
        <DataTemplate DataType="{x:Type local:ViewModel1}">
            <TextBlock>view model 1</TextBlock>
        </DataTemplate>
        <DataTemplate DataType="{x:Type local:ViewModel2}">
            <TextBlock>view model 2</TextBlock>
        </DataTemplate>
    </Window.Resources>
    <Grid>
        <ItemsControl x:Name="MyItemsControl" Loaded="MyItemsControl_Loaded">
        </ItemsControl>
    </Grid>
</Window>

Код программной части C#

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }
    private void MyItemsControl_Loaded(object sender, RoutedEventArgs e)
    {
        var oc = new ObservableCollection<ViewModelBase>();
        oc.Add(new ViewModel1());
        oc.Add(new ViewModel2());
        MyItemsControl.ItemsSource = oc;
        Dispatcher.BeginInvoke(new Action(() =>
        {
            var container = (ContentPresenter)MyItemsControl.ItemContainerGenerator.ContainerFromItem(oc[0]);
            // here container.ContentTemplate is null
            Debugger.Break();
        }), System.Windows.Threading.DispatcherPriority.Loaded);
    }
}
public class ViewModelBase
{
}
public class ViewModel1 : ViewModelBase
{
}
public class ViewModel2 : ViewModelBase
{
}

Другой важный мой вопрос здесь.

Спасибо.

Обновление 1

  1. В моей реальной программе есть более сложные DataTemplateс. ВTextBlock это просто пример.
  2. мне нужно ContentTemplate чтобы найти для конкретного контейнера / элемента / индекса DataTemplateчто было использовано. Я использую несколькоDataTemplates применяется автоматически на основе их DataType.

Обновление 2

мне нужно DataTemplates для отображения в окне настроек приложения различных элементов управления в ItemsControl, каждый с DataContext установить экземпляр подтипа ViewModel для каждого типа настройки, например CheckBoxSettingDataVM, AudioFileSettingDataVM и т. д. все наследуются от SettingDataVM.

Обновление 3

Я не хочу назначать ContentTemplate свойство явно, я хочу получить его, чтобы из элемента (ViewModel) я мог получить контейнер (типа ContentPresenter) и из него я могу получить корневой элемент внутри неявного DataTemplate для ViewModel, который может быть AudioFileSelector, ImageFileSelectorили другого типа. мне нужноContentTemplate свойство отличаться от null так что я могу сохранить ссылку на AudioFileSelector и к ImageFileSelectorи, возможно, другие в будущем. Я буду использовать эти ссылки, чтобы загрузить некоторые настройки из открытого файла приложения в этиControlс.

Может я что-то делаю не так, но я все еще изучаю MVVM. Думаю, моя проблема была бы решена, если бы я мог установитьDataType из DataTemplate и даже если у него есть ключ ресурса, он все равно будет автоматически применяться внутри ItemsControls в их сфере.

Обновление 4

Я попытался лучше понять, составив эту схему, надеюсь, это поможет (я понял, что это просто сложные вещи, но это часть моего вопроса.):

1 ответ

Решение

Из вашего кода программной части вы можете получить корневой визуальный объект созданного DataTemplate по ItemsControl для данного объекта ViewModel, выполнив следующие действия:

//Assuming you have access to a viewModel variable and to your MyItemsControl:
//We retrieve the generated container
var container = MyItemsControl.ItemContainerGenerator.ContainerFromItem(viewModel) as FrameworkElement;
//We retrieve the closest ContentPresenter in the visual tree
FrameworkElement firstContentPresenter = FindVisualSelfOrChildren<ContentPresenter>(container);
//We get the first child which is the root of the DataTemplate
FrameworkElement visualRoot = (FrameworkElement)VisualTreeHelper.GetChild(firstContentPresenter, 0); //this is what you want

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

/// <summary>
/// Parses the visual tree down looking for the first descendant (or self if correct type) of the given type.
/// </summary>
/// <typeparam name="T">Type of the descendant to find in the visual tree</typeparam>
/// <param name="child">Visual element to find descendant of</param>
/// <returns>First visual descendant of the given type or null. Can be the passed object itself if type is correct.</returns>
public static T FindVisualSelfOrChildren<T>(DependencyObject parent) where T : DependencyObject {
    if (parent == null) {
        //we've reached the end of the tree
        return null;
    }
    if (parent is T) {
        return parent as T;
    }
    //We get the immediate children
    IEnumerable<DependencyObject> children = Enumerable.Range(0, VisualTreeHelper.GetChildrenCount(parent)).Select(i => VisualTreeHelper.GetChild(parent, i));
    //We parse them to get the first child of correct type
    foreach (var child in children) {
        T result = FindVisualSelfOrChildren<T>(child);
        if (result != null) {
            return result as T;
        }
    }
    //Nothing found
    return null;
}
Другие вопросы по тегам