Как получить корневой элемент внутри примененного DataTemplate, если у него нет ключа ресурса?
Я хочу получить корневой элемент внутри применяемого DataTemplate
. Я пробовал это, но у меня это не работает, потому что дляContentPresenter
вернулся MyItemsControl.ItemContainerGenerator.ContainerFromItem(vm)
где vm
это ViewModel, ContentPresenter.ContentTemplate
является null
, хотя ContentPresenter.Content
- соответствующие данные (та же ViewModel).
Я бы получил доступ DataTemplate
s как ресурсы, как здесь, но я не могу датьDataTemplate
s, потому что я хочу, чтобы они автоматически применялись ко всем элементам внутри 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
- В моей реальной программе есть более сложные
DataTemplate
с. ВTextBlock
это просто пример. - мне нужно
ContentTemplate
чтобы найти для конкретного контейнера / элемента / индексаDataTemplate
что было использовано. Я использую несколькоDataTemplate
s применяется автоматически на основе ихDataType
.
Обновление 2
мне нужно DataTemplate
s для отображения в окне настроек приложения различных элементов управления в ItemsControl
, каждый с DataContext
установить экземпляр подтипа ViewModel для каждого типа настройки, например CheckBoxSettingDataVM
, AudioFileSettingDataVM
и т. д. все наследуются от SettingDataVM
.
Обновление 3
Я не хочу назначать ContentTemplate
свойство явно, я хочу получить его, чтобы из элемента (ViewModel) я мог получить контейнер (типа ContentPresenter
) и из него я могу получить корневой элемент внутри неявного DataTemplate
для ViewModel, который может быть AudioFileSelector
, ImageFileSelector
или другого типа. мне нужноContentTemplate
свойство отличаться от null
так что я могу сохранить ссылку на AudioFileSelector
и к ImageFileSelector
и, возможно, другие в будущем. Я буду использовать эти ссылки, чтобы загрузить некоторые настройки из открытого файла приложения в этиControl
с.
Может я что-то делаю не так, но я все еще изучаю MVVM. Думаю, моя проблема была бы решена, если бы я мог установитьDataType
из DataTemplate
и даже если у него есть ключ ресурса, он все равно будет автоматически применяться внутри ItemsControl
s в их сфере.
Обновление 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;
}