如何在 DataTemplate 的 DataType 属性中引用泛型类型?

Posted

技术标签:

【中文标题】如何在 DataTemplate 的 DataType 属性中引用泛型类型?【英文标题】:How to reference a generic type in the DataType attribute of a DataTemplate? 【发布时间】:2012-04-17 19:27:42 【问题描述】:

我有一个这样定义的 ViewModel:

public class LocationTreeViewModel<TTree> : 
    ObservableCollection<TTree>, INotifyPropertyChanged
        TTree : TreeBase<TTree>

我想在 XAML 中 DataTemplateDataType 属性中引用它。我该怎么做?

【问题讨论】:

Can I specify a generic type in XAML?的可能重复 试试 x:TypeArgument 【参考方案1】:

不,您不能在 XAML 中表达泛型类型。您将必须创建一个扩展通用类型的具体类型...

public class FooLocationTreeViewModel : LocationTreeViewModel<Foo>


【讨论】:

我成功地使用了这种技术,但最终built a generic wrapper, see my answer expounding on this IMO 你应该更新你的答案。因为您可以在 xaml 中表达一个封闭的泛型类型(或多或少)。在这里查看我的答案:***.com/a/54124755/1353211【参考方案2】:

在 XAML 2006 中,这不受支持。但是,如果您想拥有此功能,您可以自行开发。

This link 有一个很好的关于创建标记扩展的教程。

用法如下:

<Grid xmlns:ext="clr-namespace:CustomMarkupExtensions">
  <TextBlock Text="ext:GenericType FooLocationTreeViewModel(Of Foo)" />
</Grid>

您必须选择并实现语法。我建议使用 VB 表示法,因为它不会像 C# 表示法对 那样干扰。

【讨论】:

不能作为DataTemplateDataType 位工作,不允许标记扩展【参考方案3】:

我知道,我参加聚会有点晚了,但我想为将来可能会看到这个问题的所有人发布一个答案:

有可能。

你可以在这个问题的答案中看到完整的代码:DataTemplates and Generics。但由于它很长,我将复制重要的部分。如果您想了解更多详细信息,请查看引用的问题。

    你需要写一个MarkupExtension,它可以提供一个封闭的泛型类型。

    public class GenericType : MarkupExtension
    
        public GenericType()  
    
        public GenericType(Type baseType, params Type[] innerTypes)
        
            BaseType = baseType;
            InnerTypes = innerTypes;
        
    
        public Type BaseType  get; set; 
    
        public Type[] InnerTypes  get; set; 
    
        public override object ProvideValue(IServiceProvider serviceProvider)
        
            Type result = BaseType.MakeGenericType(InnerTypes);
            return result;
        
    
    

    现在您可以在 xaml 中定义关闭泛型类型的类型,然后将关闭的泛型类型用作 DataTypeDataTemplate

    <Window.Resources>
        <x:Array Type="x:Type System:Type" 
                 x:Key="ListWithTwoStringTypes">
            <x:Type TypeName="System:String" />
            <x:Type TypeName="System:String" />
        </x:Array>
    
        <WpfApp1:GenericType BaseType="x:Type TypeName=Generic:Dictionary`2" 
                           InnerTypes="StaticResource ListWithTwoStringTypes"
                           x:Key="DictionaryStringString" />
    
        <DataTemplate DataType="StaticResource DictionaryStringString">
            <TextBlock Text="Hi Dictionary"
                   FontSize="40"
                   Foreground="Cyan"/>
        </DataTemplate>
    </Window.Resources>
    

    很高兴定义的DataTemplate 被 WPF 自动选中。

【讨论】:

【参考方案4】:

迟到且不完全是问题的答案(CollinE 和 Bas 已经说过这实际上是不可能的)......但是,也许替代解决方案可能对其他人有帮助:

可以使用这样的 TemplateSelector 来解析泛型类型:

模板选择器

public class MyTemplateSelector : DataTemplateSelector

    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    
        var genericType = typeof(MyGenericType<>);
        var isMyGeneric = item?.GetType().GetGenericTypeDefinition() == genericType;

        return isMyGeneric ? MyTemplate : OtherTemplate;
    

    public DataTemplate MyTemplate  get; set; 
    public DataTemplate OtherTemplate  get; set; 

XAML

<UserControl.Resources>
        <DataTemplate x:Key="MyTemplate">            
                <!-- Set Up MyTemplate -->
        </DataTemplate>
        <DataTemplate x:Key="OtherTemplate">
            <!-- Set Up OtherTemplate -->
        </DataTemplate>
        <local:MyTemplateSelector x:Key="MyTemplateSelector"
                                MyTemplate="StaticResource MyTemplate"
                                OtherTemplate="StaticResource MyTemplate" />
</UserControl.Resources>

...

<ContentControl ContentTemplateSelector="StaticResource MyTemplateSelector" 
                Content="Binding ViewModel" />

【讨论】:

【参考方案5】:

我刚刚实现了一个肯定不够完美的解决方法,并且确实需要在您的 ViewModel 中添加一些代码(因为 VM 不应该知道视图,因此会破坏严格的 MVVM)。

定义你的泛型类型,然后用最低共同祖先定义该类型的类作为类型参数:

class GenericClass<T>  

class Class1 : GenericClass<Apples>  

class Class2 : GenericClass<Oranges>  

class WorkaroundClass : GenericClass<Fruit>  

在您的视图模型中,您需要将绑定的成员声明为祖先类型,并将其强制转换。

// instead of:
// Apple DisplayFruit => GetGrannySmith();

Fruit DisplayFruit => (Fruit)GetGrannySmith();

在您的 xaml 中,您可以将数据模板绑定到祖先类:

<DataTemplate DataType="x:Type WorkaroundClass"/>

我很确定,因为 Generic 父级是通用的,所以您不应该遇到类型参数之间的差异导致任何问题的任何实际情况。

【讨论】:

【参考方案6】:

以下解决方案对我有用:

<DataTemplate>
    <DataTemplate.DataType>
        <x:Type Type="ns:MyGenericClass`1"/>
    </DataTemplate.DataType>
</DataTemplate>

将 `1 替换为您拥有的通用参数的数量,例如:

public class MyGenericClass<TParent, TChild>

将被声明:

<x:Type Type="ns:MyGenericClass`2"/>

【讨论】:

【参考方案7】:

x:Type 标记扩展支持允许将泛型类型参数指定为括号中的逗号分隔列表。

这是一个例子:

<UserControl x:Class="Test"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:generic="clr-namespace:System.Collections.Generic;assembly=mscorlib"
        xmlns:sys="clr-namespace:System;assembly=mscorlib">
    <UserControl.Resources>
        <DataTemplate DataType="x:Type generic:List(sys:Int64)">
            <TextBlock Text="Binding Count"/>
        </DataTemplate>
    </UserControl.Resources>
</UserControl>

我在 VS 2015 上使用 .Net 4.5,因此您的里程可能会有所不同。

【讨论】:

这无法编译,至少在 VS2017 中的框架 4.7.2 中无法编译。而且我只发现在x:TypeArguments 文档中的括号中提到了逗号分隔的列表,但x:Type 没有。【参考方案8】:

我能做到这一点的唯一方法是使用MarkupExtensions

public class GenericType : MarkupExtension

     private readonly Type _of;
     public GenericType(Type of)
     
         _of = of;
     
     public override object ProvideValue(IServiceProvider serviceProvider)
     
         return typeof(LocationTreeViewModel<>).MakeGenericType(_of);
     

要使用它,我只需要这样做:

<DataTemplate DataType="app:GenericType app:TreeBaseClass">

【讨论】:

@franssu:感谢您的评论,但我不明白您的意思?我不想对每个通用类都使用它,我认为它甚至不可能!你有更好的解决方案吗? -1:不幸的是,这会触发 MC 错误; DataTemplateDataType 只能接受一组预定义的扩展名[例如x:Type]。吸@ColinE's answer是我的结论 这个答案值得一票,因为它在理论上是可能的。 -4 票是不公平的。很难回答这些问题 - 请继续关注。【参考方案9】:

略微改进的 MarkupExtension 版本,适用于最多 3 个泛型参数的类。

  public class GenericTypeExtension : MarkupExtension
  
    public GenericTypeExtension()
    

    
    public GenericTypeExtension(string baseTypeName_, Type genericType1_, Type genericType2_, Type genericType3_)
    
      BaseTypeName = baseTypeName_;
      GenericType1 = genericType1_;
      GenericType2 = genericType2_;
      GenericType3 = genericType3_;
    
    public string BaseTypeName  get; set; 
    public string BaseTypeAssemblyName  get; set; 
    public Type GenericType1  get; set; 
    public Type GenericType2  get; set; 
    public Type GenericType3  get; set; 

    public override object ProvideValue(IServiceProvider serviceProvider_)
    
      var list = new List<Type>();
      if (GenericType1 != null)
      
        list.Add(GenericType1);
      
      if (GenericType2 != null)
      
        list.Add(GenericType2);
      
      if (GenericType3 != null)
      
        list.Add(GenericType3);
      

      var type = Type.GetType(string.Format("0`1, 2", BaseTypeName, list.Count, BaseTypeAssemblyName));
      if (type != null)
      
        return type.MakeGenericType(list.ToArray());
      
      return null;
    

  

【讨论】:

这不回答 OP - 对数据模板没有用 其实这样的。然后在 DataTemplate 的 DataType 字段中使用标记扩展。

以上是关于如何在 DataTemplate 的 DataType 属性中引用泛型类型?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Silverlight 中使用 DataTemplate 显示单个项目?

[UWP]如何使用代码创建DataTemplate(或者ControlTemplate)

如何使用 DataTemplate 在 ListBox 中进行自定义显示?

如何设置控件属性(在DataTemplate和UserControl中)的绑定以使用ItemSource的给定属性?

如何在 C# 中为自定义 DataTemplateSelector 获取 DataTemplate 的 x:DataType

如何在 DataTemplate 的 DataType 属性中引用泛型类型?