1

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 DataTemplates as resources like here but I cannot give the DataTemplates 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.

C# from my actual project

(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;
    }
}

Test example

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# code-behind

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.

Update 1

  1. In my actual program I have more complex DataTemplates. The TextBlock is just an example.
  2. I need a ContentTemplate to find, for a specific container/item/index, the DataTemplate that was used. I use multiple DataTemplates applied automatically based on their DataType.

Update 2

I need DataTemplates 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.

Update 3

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 Controls.

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 ItemsControls in their scope.

Update 4

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.):

schema

silviubogan
  • 3,343
  • 3
  • 31
  • 57
  • I have updated the question with more details. Thank you very much. – silviubogan Sep 17 '19 at 12:40
  • The container has no reference to the `DataTemplate` that was used to create the root element. If you need a reference to this, you are probably doing something wrong. – mm8 Sep 17 '19 at 12:40
  • Sounds like a [XY](https://en.wikipedia.org/wiki/XY_problem) problem. Why don't you explain what you want to achieve with the datatemplate or root element. Perhaps there is an easier way. – Nawed Nabi Zada Sep 17 '19 at 12:43
  • Why do you need the `DataTemplate`? – mm8 Sep 17 '19 at 12:50
  • 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`. Thank you. – silviubogan Sep 17 '19 at 12:54
  • Sorry, I still don't understand what you are trying to do but implicit data templates, i.e. those without an `x:Key`, will be resolved automatically provided that they are in scope. There is no reason to assign the `ContentTemplate` property explicitly. – mm8 Sep 17 '19 at 13:06
  • @mm8 I have updated my question again. Thank you. – silviubogan Sep 17 '19 at 13:20
  • So, we assume that you have access in your code to a viewmodel object, and you wish to retrieve the root visual element of the datatemplate that was applied to this viewmodel object when generated by the itemscontrol? – Corentin Pane Sep 17 '19 at 14:15
  • good, but this is reserved for advanced behaviours only, you should not need to retrieve this object programmatically. – Corentin Pane Sep 17 '19 at 14:35
  • @CorentinPane How can I use MVVM with settings stored in a file (for example .xml)? I posted more source code [here](https://stackoverflow.com/q/57971712/258462). Thank you. – silviubogan Sep 17 '19 at 14:42

1 Answers1

1

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;
}
Corentin Pane
  • 4,794
  • 1
  • 12
  • 29