当应用的 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.ContentTemplate
是 null
,虽然ContentPresenter.Content
是对应的数据(同一个ViewModel)。
我会像 here 这样的资源访问 DataTemplate
s 但我不能给 DataTemplate
s 资源密钥,因为我希望它们自动应用于 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
- 在我的实际程序中,我有更复杂的
DataTemplate
。 TextBlock
只是一个例子。
- 我需要
ContentTemplate
来查找特定 container/item/index 所使用的 DataTemplate
。我根据 DataType
. 使用多个 DataTemplate
自动应用
更新 2
我需要 DataTemplate
s 在应用程序的设置 window 中显示 ItemsControl
中的不同控件,每个 DataContext
设置为一个实例每种设置类型的 ViewModel 子类型,例如CheckBoxSettingDataVM
、AudioFileSettingDataVM
等都继承自 SettingDataVM
.
更新 3
我不想明确分配 ContentTemplate
属性,我想得到它,从一个项目 (ViewModel) 我可以得到容器(类型 ContentPresenter
) 并且我可以从中获取 ViewModel 的隐式 DataTemplate
内的根元素,它可以是 AudioFileSelector
、ImageFileSelector
或其他类型。我需要 ContentTemplate
属性 与 null
不同,这样我就可以存储对 AudioFileSelector
和 ImageFileSelector
以及将来可能其他人的引用。我将使用这些引用将应用程序打开文件中的一些设置加载到这些 Control
s.
也许我做错了什么,但我还在学习MVVM。我认为如果我可以设置 DataTemplate
的 DataType
我的问题就会得到解决,即使它有资源键,它仍然会自动应用在 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;
}
我希望在已应用的 DataTemplate
中获取根元素。我试过 this 但它对我不起作用,因为对于 MyItemsControl.ItemContainerGenerator.ContainerFromItem(vm)
返回的 ContentPresenter
其中 vm
是 ViewModel,ContentPresenter.ContentTemplate
是 null
,虽然ContentPresenter.Content
是对应的数据(同一个ViewModel)。
我会像 here 这样的资源访问 DataTemplate
s 但我不能给 DataTemplate
s 资源密钥,因为我希望它们自动应用于 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
- 在我的实际程序中,我有更复杂的
DataTemplate
。TextBlock
只是一个例子。 - 我需要
ContentTemplate
来查找特定 container/item/index 所使用的DataTemplate
。我根据DataType
. 使用多个
DataTemplate
自动应用
更新 2
我需要 DataTemplate
s 在应用程序的设置 window 中显示 ItemsControl
中的不同控件,每个 DataContext
设置为一个实例每种设置类型的 ViewModel 子类型,例如CheckBoxSettingDataVM
、AudioFileSettingDataVM
等都继承自 SettingDataVM
.
更新 3
我不想明确分配 ContentTemplate
属性,我想得到它,从一个项目 (ViewModel) 我可以得到容器(类型 ContentPresenter
) 并且我可以从中获取 ViewModel 的隐式 DataTemplate
内的根元素,它可以是 AudioFileSelector
、ImageFileSelector
或其他类型。我需要 ContentTemplate
属性 与 null
不同,这样我就可以存储对 AudioFileSelector
和 ImageFileSelector
以及将来可能其他人的引用。我将使用这些引用将应用程序打开文件中的一些设置加载到这些 Control
s.
也许我做错了什么,但我还在学习MVVM。我认为如果我可以设置 DataTemplate
的 DataType
我的问题就会得到解决,即使它有资源键,它仍然会自动应用在 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;
}