如何在数据模板的数据类型属性中引用泛型类型

本文关键字:属性 数据类型 引用 泛型类型 数据 | 更新日期: 2023-09-27 18:29:51

我有一个像这样定义的ViewModel:

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

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

如何在数据模板的数据类型属性中引用泛型类型

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

public class FooLocationTreeViewModel : LocationTreeViewModel<Foo>
{
}
我知道

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

这是可能的。

您可以在此问题的答案中看到整个代码:DataTemplates 和 Generics。但由于它很长,我将只复制重要的部分。如果您想了解更多细节,请查看参考的问题。

  1. 您需要编写一个可以提供封闭泛型类型的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;
        }
    }
    
  2. 现在,您可以定义在 xaml 中关闭泛型类型的类型,然后将封闭的泛型类型用作DataTemplateDataType

    <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>
    
  3. 很高兴 WPF 会自动选择定义的DataTemplate

在 XAML 2006 中不支持此功能。但是,如果您想拥有此功能,您可以自己动手。

此链接有一个关于创建标记扩展的很好的教程。

用法是这样的:

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

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

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

可以使用如下所示的模板选择器解析泛型类型:

模板选择器

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

我刚刚实现了一个解决方法,该解决方法肯定不完美,并且确实需要 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}"/>

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

以下解决方案对我有用:

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

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

public class MyGenericClass<TParent, TChild>

将被声明:

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

令人惊讶的是,这工作正常:

<DataTemplate DataType="GenericClass&lt;TypeArgument1,TypeArgument2&gt;">

只需复制 C# 类型并将<替换为 &lt;>替换为 &gt;(这些是 XML 转义序列(

{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,所以你的里程可能会有所不同。

我能做到这一点的唯一方法是使用 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}">

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