没有资源密钥时,如何在应用的DataTemplate中获取根元素? [英] How to get the root element inside an applied DataTemplate when it does not have a resource key?
问题描述
我希望将根元素放入已应用的DataTemplate
中.我尝试了此,但它对我不起作用,因为对于MyItemsControl.ItemContainerGenerator.ContainerFromItem(vm)
返回的ContentPresenter
,其中vm
是ViewModel,ContentPresenter.ContentTemplate
是null
,尽管ContentPresenter.Content
是对应的数据(相同的ViewModel ).
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).
我会以此处之类的资源访问DataTemplate
,但是我无法提供DataTemplate
的资源密钥,因为我希望将它们自动应用于ItemsControl
中的所有项目.因此,我必须找到一种从ItemsControl
中的项目获取DataTemplate
的方法.
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
.
我可以使用if
-else
来确定vm.GetType()
的功能中的DataTemplate
资源,但是我希望在没有ItemContainerGenerator
的情况下并根据MVVM模式,实现我希望的功能,并且没有硬编码类型.
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.
以下是我认为与代码相关的内容.例如,我使用MainWindow
中的MyAudioFileSelector
将数据文件中的某些设置加载到UI中,我不确定MVVM的操作方式是什么.
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.
(我现在假设只有一个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
{
}
我的另一个相关问题是此处.
Another relevant question of mine is here.
谢谢.
- 在我的实际程序中,我有更复杂的
DataTemplate
.TextBlock
只是一个例子. - 我需要一个
ContentTemplate
来为特定的容器/项目/索引查找所使用的DataTemplate
.我使用了多个根据其DataType
自动应用的DataTemplate
.
- In my actual program I have more complex
DataTemplate
s. TheTextBlock
is just an example. - I need a
ContentTemplate
to find, for a specific container/item/index, theDataTemplate
that was used. I use multipleDataTemplate
s applied automatically based on theirDataType
.
更新2
我需要DataTemplate
在应用程序的设置"窗口中显示ItemsControl
中的不同控件,每个控件的DataContext
设置为每种设置类型的ViewModel子类型的实例. CheckBoxSettingDataVM
,AudioFileSettingDataVM
等都从SettingDataVM
继承.
Update 2
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
.
我不想显式地分配ContentTemplate
属性,因此我想从项目(ViewModel)中获取它,我可以获取容器(类型为ContentPresenter
),并从中获取根元素在ViewModel的隐式DataTemplate
中,可以为AudioFileSelector
,ImageFileSelector
或其他类型.我需要ContentTemplate
属性与null
不同,以便将来可以存储对AudioFileSelector
和ImageFileSelector
以及其他引用的引用.我将使用这些引用将应用程序打开的文件中的某些设置加载到这些Control
中.
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.
也许我做错了什么,但我仍在学习MVVM.我认为,如果我可以设置DataTemplate
的DataType
,并且即使它具有资源密钥,它也将自动应用于它们范围内的ItemsControl
内.
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.):
推荐答案
通过以下代码,您可以通过ItemsControl
为给定的ViewModel对象检索实例化的DataTemplate
的根可视对象,方法如下: :
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;
}
这篇关于没有资源密钥时,如何在应用的DataTemplate中获取根元素?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!