当应用的 DataTemplate 没有资源键时,如何获取根元素?

How to get the root element inside an applied DataTemplate when it does not have a resource key?

我希望在已应用的 DataTemplate 中获取根元素。我试过 this 但它对我不起作用,因为对于 MyItemsControl.ItemContainerGenerator.ContainerFromItem(vm) 返回的 ContentPresenter 其中 vm 是 ViewModel,ContentPresenter.ContentTemplatenull,虽然ContentPresenter.Content是对应的数据(同一个ViewModel)。

我会像 here 这样的资源访问 DataTemplates 但我不能给 DataTemplates 资源密钥,因为我希望它们自动应用于 ItemsControl。所以我必须找到一种方法从 ItemsControl.

中的项目中获取 DataTemplate

我可以使用 if-else 来确定 vm.GetType() 函数中的 DataTemplate 资源,但我想在没有 [=29 的情况下实现我的愿望=] 并根据 MVVM 模式,如果可能,并且没有硬编码类型。

以下是我认为与代码相关的内容。例如,我使用 MainWindow 中的 MyAudioFileSelector 将数据文件中的一些设置加载到 UI 中,但我不确定 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. 在我的实际程序中,我有更复杂的 DataTemplateTextBlock 只是一个例子。
  2. 我需要 ContentTemplate 来查找特定 container/item/index 所使用的 DataTemplate。我根据 DataType.
  3. 使用多个 DataTemplate 自动应用

更新 2

我需要 DataTemplates 在应用程序的设置 window 中显示 ItemsControl 中的不同控件,每个 DataContext 设置为一个实例每种设置类型的 ViewModel 子类型,例如CheckBoxSettingDataVMAudioFileSettingDataVM 等都继承自 SettingDataVM.

更新 3

我不想明确分配 ContentTemplate 属性,我想得到它,从一个项目 (ViewModel) 我可以得到容器(类型 ContentPresenter ) 并且我可以从中获取 ViewModel 的隐式 DataTemplate 内的根元素,它可以是 AudioFileSelectorImageFileSelector 或其他类型。我需要 ContentTemplate 属性 与 null 不同,这样我就可以存储对 AudioFileSelectorImageFileSelector 以及将来可能其他人的引用。我将使用这些引用将应用程序打开文件中的一些设置加载到这些 Controls.

也许我做错了什么,但我还在学习MVVM。我认为如果我可以设置 DataTemplateDataType 我的问题就会得到解决,即使它有资源键,它仍然会自动应用在 ItemsControl 中他们的范围。

更新 4

我试图通过制定这个方案来更好地理解,希望它能有所帮助(我意识到这只是复杂的事情,但这是我问题的一部分。):

从您的代码隐藏中,您可以通过执行以下操作检索给定 ViewModel 对象的 ItemsControl 实例化 DataTemplate 的根视觉对象:

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