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

30

我有一个定义如下的ViewModel:

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

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


可能是在XAML中指定泛型类型的方法?的重复问题。 - franssu
尝试 x:TypeArgument - macio.Jun
11个回答

16

不,你不能在XAML中表示泛型类型。你需要创建一个扩展你的泛型类的具体类型...

public class FooLocationTreeViewModel : LocationTreeViewModel<Foo>
{
}

我成功地使用了这种技巧,但最终构建了一个通用的包装器,请参见我的答案详细说明 - Ruben Bartelink
我认为你应该更新你的回答。因为你可以在XAML中表达一个封闭的泛型类型(或多或少)。请参见我的答案:https://stackoverflow.com/a/54124755/1353211 - Rico-E

5
我知道我来晚了,但我想为那些未来可能看到这个问题的人发表一个答案:
是可以的。
你可以在这个问题的答案中看到完整的代码:DataTemplates and Generics。但是因为它太长了,我只会复制重要部分。如果你想要更多细节,请查看引用的问题。
  1. You need to write a MarkupExtension which can provide a closed generic type.

    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. Now you can define your type which closes your generic type in xaml, and then use the closed generic type as DataType of an DataTemplate.

    <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. Be happy that the defined DataTemplate gets automatically selected by WPF.


3
在 XAML 2006 年版本中不支持此功能。但是,如果您想要这个功能,您可以自己创建它。 这个链接有一个很好的教程,可以帮助您创建标记扩展。
使用方法如下:
<Grid xmlns:ext="clr-namespace:CustomMarkupExtensions">
  <TextBlock Text="{ext:GenericType FooLocationTreeViewModel(Of Foo)}" />
</Grid>

你需要选择并实现语法。我建议使用VB符号,因为它不会像C#符号那样与<和>产生干扰。


5
无法作为“DataTemplate”的“DataType”位不允许标记扩展。 - Ruben Bartelink

2

虽然有些晚并不是问题的答案(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}" />

1
以下解决方案对我有效:
<DataTemplate>
    <DataTemplate.DataType>
        <x:Type Type="ns:MyGenericClass`1"/>
    </DataTemplate.DataType>
</DataTemplate>

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

public class MyGenericClass<TParent, TChild>

会被声明为:

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

我得到了字符'`'在字符串'ns:MyGenericClass`1'中是意外的。无效的XAML类型名称。 - renklus

0

我刚刚实现了一个权宜之计,虽然肯定不是完美的解决方案,并且需要在您的ViewModel中添加一些代码(因为VM不应该知道视图,这会破坏严格的MVVM架构)。

定义您的泛型类型,然后使用最低公共祖先作为类型参数来定义该类型的一个类:

class GenericClass<T> { }

class Class1 : GenericClass<Apples> { }

class Class2 : GenericClass<Oranges> { }

class WorkaroundClass : GenericClass<Fruit> { }

在您的ViewModel中,您需要将绑定成员声明为祖先类型,并进行强制转换。
// instead of:
// Apple DisplayFruit => GetGrannySmith();

Fruit DisplayFruit => (Fruit)GetGrannySmith();

在你的XAML中,你可以将你的数据模板绑定到祖先类:
<DataTemplate DataType="{x:Type WorkaroundClass}"/>

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


0
令人惊讶的是,这个正常运作:
<DataTemplate DataType="GenericClass&lt;TypeArgument1,TypeArgument2&gt;">

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

0
在静态类中定义您的泛型类型。
public static GenericTypes {
  public static Type TreeViewOfITreeItem => typeof(TreeView<ITreeItem>);
}

然后在DataTemplate中使用它。
<DataTemplate DataType="{x:Static mhui:GenericTypes.TreeViewOfITreeItem}">

-1

{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,因此您的表现可能会有所不同。

7
这段代码无法编译,至少在使用VS2017中的4.7.2框架时不行。我只在“x:TypeArguments”文档中找到过括号内逗号分隔列表的提及,但在“x:Type”方面却没有。 - Clemens

-2

这是MarkupExtension的稍微改进版本,适用于具有三个泛型参数的类。

  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 的问题 - datatemplates 没有用处。 - Ruben Bartelink
实际上,这是可以的。然后您可以在DataTemplate的DataType字段中使用标记扩展。 - Mark A. Donohoe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接