29

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?

mahboub_mo
  • 2,908
  • 6
  • 32
  • 72

10 Answers10

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>
{
}
ColinE
  • 68,894
  • 15
  • 164
  • 232
  • I used this technique with success but ultimately [built a generic wrapper, see my answer expounding on this](http://stackoverflow.com/a/33827448/11635) – Ruben Bartelink Nov 20 '15 at 13:11
  • IMO you should update your answer. Because you **can** express a closed generic type in xaml (more or less). See my answer here: https://stackoverflow.com/a/54124755/1353211 – Rico-E Aug 28 '19 at 13:09
5

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.

Rico-E
  • 1,806
  • 2
  • 28
  • 47
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 >.

Bas
  • 26,772
  • 8
  • 53
  • 86
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}" />
JanDotNet
  • 3,746
  • 21
  • 30
0

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.

Jonathan Tuzman
  • 11,568
  • 18
  • 69
  • 129
0

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"/>
Steven
  • 11
  • 3
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)

Scover
  • 93
  • 9
-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.

Daniel Crowe
  • 321
  • 3
  • 9
  • 7
    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`. – Clemens Sep 08 '18 at 21:09
-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}">
mahboub_mo
  • 2,908
  • 6
  • 32
  • 72
  • @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? – mahboub_mo Oct 13 '14 at 08:31
  • 1
    -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](http://stackoverflow.com/a/10005490/11635) was my conclusion – Ruben Bartelink Nov 20 '15 at 11:54
  • 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. – John C Oct 07 '20 at 15:58
-2

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

  }
Mayur Shah
  • 11
  • 1