Как получить корневой элемент внутри прикладной таблицы данных, когда у него нет ключа ресурса?

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

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

Я мог бы использовать _13 _-_ 14_ для определения ресурса 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. В моей реальной программе у меня есть более сложные DataTemplates. TextBlock - это просто пример.
  2. Мне нужен ContentTemplate, чтобы найти для конкретного контейнера / элемента / индекса DataTemplate, который использовался. Я использую несколько DataTemplates, применяемых автоматически на основе их DataType.

Обновление 2.

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

Обновление 3.

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

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

Обновление 4.

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

schema


person silviubogan    schedule 17.09.2019    source источник
comment
Я обновил вопрос, добавив более подробную информацию. Большое Вам спасибо.   -  person silviubogan    schedule 17.09.2019
comment
Контейнер не имеет ссылки на DataTemplate, который использовался для создания корневого элемента. Если вам нужна ссылка на это, вы, вероятно, делаете что-то не так.   -  person mm8    schedule 17.09.2019
comment
Похоже, как xy проблема. Почему бы вам не объяснить, что вы хотите достичь с DataTemplate или Coot Element. Возможно, есть проще.   -  person Nawed Nabi Zada    schedule 17.09.2019
comment
Зачем тебе DataTemplate?   -  person mm8    schedule 17.09.2019
comment
Мне нужен DataTemplateS для отображения в окне настроек приложения разных элементов управления в ItemsControl, каждый с DataContext, установленным на экземпляр подтипов ViewModel для каждого типа настройки, например, CheckBoxSettingDataVM, AudioFileSettingDataVM и т. Д. Все наследует от SettingDataVM. Спасибо.   -  person silviubogan    schedule 17.09.2019
comment
Извините, я все еще не понимаю, что вы пытаетесь сделать, но неявные шаблоны данных, то есть без x:Key, будут разрешены автоматически при условии, что они входят в область действия. Нет причин назначать ContentTemplate свойство явно.   -  person mm8    schedule 17.09.2019
comment
@ mm8 Я снова обновил свой вопрос. Спасибо.   -  person silviubogan    schedule 17.09.2019
comment
Итак, мы предполагаем, что у вас есть доступ в вашем коде к объекту viewmodel, и вы хотите получить корневой визуальный элемент datatemplate, который был применен к этому объекту viewmodel при создании элементом управления itemscontrol?   -  person Corentin Pane    schedule 17.09.2019
comment
хорошо, но это зарезервировано только для расширенного поведения, вам не нужно извлекать этот объект программно.   -  person Corentin Pane    schedule 17.09.2019
comment
@CorentinPane Как я могу использовать MVVM с настройками, хранящимися в файле (например, .xml)? Дополнительный исходный код я разместил здесь. Спасибо.   -  person silviubogan    schedule 17.09.2019


Ответы (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;
}
person Corentin Pane    schedule 17.09.2019