How to reference a generic type in the DataType attribute of a DataTemplate?
Asked Answered
L

13

31

I have a ViewModel defined like this:

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

I want to reference it in the DataType attribute of a DataTemplate in XAML. How can I do that?

Lamonicalamont answered 4/4, 2012 at 5:0 Comment(2)
possible duplicate of Can I specify a generic type in XAML?Vary
Try x:TypeArgumentLornalorne
N
16

No, you cannot express a generics type in XAML. You will have to create a concrete type that extends your generic one ...

public class FooLocationTreeViewModel : LocationTreeViewModel<Foo>
{
}
Nickola answered 4/4, 2012 at 5:38 Comment(2)
I used this technique with success but ultimately built a generic wrapper, see my answer expounding on thisPolypary
IMO you should update your answer. Because you can express a closed generic type in xaml (more or less). See my answer here: https://mcmap.net/q/271481/-datatemplates-and-genericsUpshot
U
7

I know, that I'm a little late to the party, but I want post an answer for all those who may see this question in the future:

It is possible.

You can see the whole code in the answer to this question: DataTemplates and Generics. But since it is quite long, I will just copy the important bits. If you want more details, then look into the referenced question.

  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.

Upshot answered 5/8, 2019 at 15:46 Comment(0)
F
3

In XAML 2006 this is not supported. You can, however, roll your own if you want to have this functionality.

This link has a nice tutorial on creating markup extensions.

Usage would be like this:

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

You have to choose and implement the syntax though. I suggest the VB notation since it won't interfere like the C# notation does with < and >.

Fishworm answered 4/4, 2012 at 5:46 Comment(1)
wont work as the DataType bit of a DataTemplate doesnt permit markup extensionsPolypary
B
2

Late and not exactly the answer to the question (CollinE and Bas already sayed the it is actually not possible)... However, maybe the alternativ solution may be helpful for others:

It is possible to resolve generic types by using a TemplateSelector like that:

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}" />
Burglar answered 14/4, 2020 at 8:24 Comment(0)
H
2

The following solution worked for me:

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

Replace `1 for the amount of generic parameters you have, e.g.:

public class MyGenericClass<TParent, TChild>

would be declared:

<x:Type Type="ns:MyGenericClass`2"/>
Horning answered 18/11, 2021 at 13:47 Comment(1)
I get Character '`' was unexpected in string 'ns:MyGenericClass`1'. Invalid XAML type name.Stomatitis
M
1

I just implemented a workaround that is certainly less than perfect, and does require a bit of code in your ViewModel (which. because the VM shouldn't know about the view, breaks strict MVVM).

Define your generic type, and then define a class of that type with the lowest-common-ancestor as the type argument:

class GenericClass<T> { }

class Class1 : GenericClass<Apples> { }

class Class2 : GenericClass<Oranges> { }

class WorkaroundClass : GenericClass<Fruit> { }

In your viewmodel you'll need to declare your bound member as the ancestor type, and cast it.

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

Fruit DisplayFruit => (Fruit)GetGrannySmith();

In your xaml, you can then bind your data template to the ancestor class:

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

I'm pretty sure that because the Generic parent is, well, generic, you shouldn't run into any actual cases where the differences between your type arguments cause any problems.

Messidor answered 5/6, 2020 at 13:31 Comment(0)
Q
1

Define your generic types in the static class.

public static GenericTypes {
  public static Type TreeViewOfITreeItem => typeof(TreeView<ITreeItem>);
}

And then use it in the DataTemplate.

<DataTemplate DataType="{x:Static mhui:GenericTypes.TreeViewOfITreeItem}">
Quartic answered 11/9, 2023 at 8:46 Comment(0)
C
0

Suprisingly, this works properly:

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

Just copy the C# type and replace < by &lt; and > by &gt; (those are XML escape sequences)

Compile answered 4/6, 2023 at 11:40 Comment(0)
A
0

Going to expand a bit on answers provided @Steven and @ColinE.

If you're going to use a generic definition (i.e. GenericType<> without a generic parameter) then @Steven 's answer will work.

If you're going to use a specific generic parameter (i.e GenericType<GenericParameter>, your best bet is to create a concrete type like @ColinE suggests (i.e. class GenericTypeImpl : GenericType<GenericParameter

Additionally, make sure that your ` between the type and number at the end of the type is a backquote and not an apostrophe, otherwise you'll get a compile-time error. If you're using an American QWERTY keyboard, you can type the backquote by typing Shift + ~ (Tilde).

Anabasis answered 26/4 at 18:47 Comment(0)
M
0

Mild improvement to a previous answer by Rico-E.

/// <summary>
/// Markup extension for using generic types in xaml
/// Source: https://mcmap.net/q/271299/-how-to-reference-a-generic-type-in-the-datatype-attribute-of-a-datatemplate
/// </summary>
public class GenericType : MarkupExtension
{
    public GenericType() { }

    public GenericType(Type baseType, params Type[] innerTypes)
    {
        BaseType = baseType;
        InnerTypes = innerTypes;
    }

    public Type BaseType { get; set; }
    public Type InnerType
    {
        set
        {
            InnerTypes = [value];
        }
    }

    public Type[] InnerTypes { get; set; }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return BaseType.MakeGenericType(InnerTypes);
    }
}

This one supports single generic types. So you can simply use the MarkupExtension in your xaml like this:

<ui:GenericType
    x:Key="DefaultEnumPair"
    BaseType="{x:Type dt:EnumPair`1}"
    InnerType="{x:Type sys:Enum}" />
Mammon answered 12/6 at 13:0 Comment(0)
L
-1

Slightly improved version of MarkupExtension, work for classes upto 3 generic parameters.

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

  }
Listel answered 22/1, 2015 at 10:27 Comment(2)
This does not answer the OP - no use for datatemplatesPolypary
Actually, this kind of does. You then use the markup extension in the DataType field of the DataTemplate.Beals
L
-1

The {x:Type} markup extension supports allows generic type arguments to be specified as a comma separated list in parentheses.

Here's an example:

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

I am using .Net 4.5 on VS 2015, so your mileage may vary.

Lathy answered 6/6, 2018 at 2:17 Comment(1)
This does not compile, at least not with framework 4.7.2 in VS2017. And I only found mention of a comma separated list in parentheses in the x:TypeArguments documentation, but not for x:Type.Gyve
L
-2

The only way i could do this is to use 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);
     }
}

And to use it i just need to do this:

<DataTemplate DataType="{app:GenericType app:TreeBaseClass}">
Lamonicalamont answered 12/12, 2013 at 12:42 Comment(3)
@franssu:Thanks for your comment,but i don't understand what you mean?i didnt want to use this for every generic classes,i don't think its even possible! Do you have a better solution?Lamonicalamont
-1: Unfortunately this triggers a MC error; DataTemplate's DataType can only accept a predefined set of extensions [such as x:Type]. Suck up @ColinE's answer was my conclusionPolypary
This answer deserves an upvote because it's theoretically possible. And -4 votes is unfair. It's hard to answer these questions - keep em coming please.Potsdam

© 2022 - 2024 — McMap. All rights reserved.