I wish to get the root element inside an applied DataTemplate
. I tried this but it does not work for me, because for the ContentPresenter
returned by MyItemsControl.ItemContainerGenerator.ContainerFromItem(vm)
where vm
is a ViewModel, ContentPresenter.ContentTemplate
is null
, although ContentPresenter.Content
is the corresponding data (the same ViewModel).
I would access DataTemplate
s as resources like here but I cannot give the DataTemplate
s resource keys because I want them to be automatically applied to all the items inside the ItemsControl
. So I must find a way to get the DataTemplate
from an item inside the ItemsControl
.
I could use if
-else
for determining the DataTemplate
resource in function of the vm.GetType()
but I would like to realize what I wish without ItemContainerGenerator
and according to the MVVM pattern, if possible, and without hard-coding types.
Below is what I think is relevant in the code. I use, for example, MyAudioFileSelector
from MainWindow
to load some settings from the data file into the UI and I am not sure what is the MVVM way of doing this.
(I suppose currently that there is only one AudioFileSelector and one ImageFileSelector, but in future probably I will have more of them.)
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;
}
}
<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>
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
{
}
Another relevant question of mine is here.
Thank you.
DataTemplate
s. The TextBlock
is just an example.ContentTemplate
to find, for a specific container/item/index, the DataTemplate
that was used. I use multiple DataTemplate
s applied automatically based on their DataType
.I need DataTemplate
s to display in a Settings window of an application different controls in an ItemsControl
, each with the DataContext
set to an instance of a ViewModel subtype for each setting type, e.g. CheckBoxSettingDataVM
, AudioFileSettingDataVM
etc. all inheriting from SettingDataVM
.
I do not want to assign the ContentTemplate
property explicitly, I want to get it so, from an item (ViewModel) I can get the container (of type ContentPresenter
) and from it I can get the root element inside the implicit DataTemplate
for the ViewModel, which can be AudioFileSelector
, ImageFileSelector
or other type. I need the ContentTemplate
property to be different than null
so I can store a reference to the AudioFileSelector
and to the ImageFileSelector
and maybe others in the future. I will use these references to load some settings from the application's opened file into these Control
s.
Maybe I am doing something wrong, but I am still learning MVVM. I think that my problem would be solved if I could set the DataType
of the DataTemplate
and, even if it has a resource key, it would still be automatically applied inside the ItemsControl
s in their scope.
I tried to understand better by making this scheme, I hope it helps (I realized it just complicated things, but it is part of my question.):
From your code-behind, you can retrieve the root visual object of an instantiated DataTemplate
by an ItemsControl
for a given ViewModel object by doing the following:
//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
You need this helper function which parses the visual tree down, looking for the first child of the correct type.
/// <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;
}