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

后端 未结 8 1418
深忆病人
深忆病人 2020-12-18 18:19

I have a ViewModel defined like this:

public class LocationTreeViewModel : 
    ObservableCollection, INotifyPropertyChanged
               


        
相关标签:
8条回答
  • 2020-12-18 18:55

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

    0 讨论(0)
  • 2020-12-18 18:57

    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.

    0 讨论(0)
  • 2020-12-18 18:58

    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.

    0 讨论(0)
  • 2020-12-18 19:00

    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}">
    
    0 讨论(0)
  • 2020-12-18 19:01

    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}" />
    
    0 讨论(0)
  • 2020-12-18 19:09

    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;
        }
    
      }
    
    0 讨论(0)
提交回复
热议问题