1

I have read nearly a thousand posts explaining that setting a closed generic type as DataType on a DataTemplate does not work, because WPF wouldn't support that. But as a matter of fact, this is just wrong.

I can define the following DataTemplate in my Window.Resources and it will be used when I assign a list of strings to a content control. For example:

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:System="clr-namespace:System;assembly=mscorlib"
        xmlns:Generic="clr-namespace:System.Collections.Generic;assembly=mscorlib"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <DataTemplate DataType="{x:Type TypeName=Generic:List`1[System.String]}">
            <TextBlock Text="Hi List of Strings"
                       FontSize="40"
                       Foreground="Cyan"/>
        </DataTemplate>
    </Window.Resources>
    <Grid>
        <ContentControl x:Name="_contentControl">
        </ContentControl>
    </Grid>
</Window>

and in code-behind:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        _contentControl.Content = new List<string> { "Huhu" };
    }
}

With this setup you will see "Hi List of Strings". For me that's the proof that I can define generic types as DataType. But I want to take it one step further: I'd like to define a Dictionary<string, string> as DataType. But unfortunately, I can't get it to work.

So the question is: How can I define a Dictionary<string, string> as DataType of a DataTemplate?

If you know the answer, you can stop reading. But since it is good practice to show what I already did, I keep writing. What did I do already? At first I went brute-force and tried several combinations similar to:

- DataType="{x:Type TypeName=Generic:Dictionary`2[System.String];[System.String]}"
- DataType="{x:Type TypeName=Generic:Dictionary`2[System.String],[System.String]}"
- DataType="{x:Type TypeName=Generic:Dictionary`2[System.String,System.String]}"

But since none of them worked, I dove into System.Xaml and looked at TypeExtension, GenericTypeNameParser and GenericTypeNameScanner, because I thought that these are the codelines which resolve the type. But looking at the code I realized that ` is an invalid character.

To prove it, I wrote my own MarkupExtension

public class UseTheTypeExtensionsParser : MarkupExtension
{
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        var a = new TypeExtension("Generic:List`1[[System.String]]");
        var type = a.ProvideValue(serviceProvider);
        return type.ToString();
    }
}

and used it as follows:

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:System="clr-namespace:System;assembly=mscorlib"
        xmlns:Generic="clr-namespace:System.Collections.Generic;assembly=mscorlib"
        xmlns:WpfApp1="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <ContentControl Content="{WpfApp1:UseTheTypeExtensionsParser}"/>
    </Grid>
</Window>

And this threw the exception that the character ` was not expected and that the XAML-type is invalid.

That got me wondering why my first example worked. I think, that on markup-compiling the XAML for WPF, it is not the TypeExtension that is used for resolving the XamlType, but i think that the XamlNamespace is used. Because this class has the MangleGenericTypeName-method which uses the `-character. But I still can't see the code which extracts the type arguments, so I cannot see the correct syntax to specify the type arguments for the Dictionary. This is where I am stuck.

(Needless to say that the Microsoft-Docs are worthless on this topic.)

Edit: Since it seems unclear why I want this, I will explain it: I want the automatic selection of a ContentTemplate of the ContentControl. And of course: my constructed DataTemplate in the example is very simple. But everyone should be able to imagine, that I want different DataTemplates for Lists, for Dictionaries or for simple strings.

I have a ViewModel which has a public object Result { get; } Property. And sometimes, the result is an int, sometimes a string, sometimes a List and so on and so forth. I am binding this Result-property to the Content-Property of a ContentControl. And for all the types mentioned, I wrote different DataTemplates which are automatically selected by WPF. So ints are shown in a Rectangle and Strings are shown in an Ellipse.

After I got all this to work, I want another DataTemplate, but this time for a Dictionary.

Rico-E
  • 1,806
  • 2
  • 28
  • 47
  • 1
    You don't have any generic types here. You have a *concrete* type, `List`. A generic type would be `List`. You can't use generic types simply because the *compiler* has no way of knowing what's included in those generic types – Panagiotis Kanavos Jan 08 '19 at 13:29
  • And no, MS docs aren't worhtless, nor are all those people saying that you can't use generic types wrong. WPF came out 10 years ago after all, someone would have noticed. *Almost all tutorials and docs* show binding to generic types anyway, whether it's a `List` or an `ObservableCollection` or something returned by EF – Panagiotis Kanavos Jan 08 '19 at 13:31
  • Never mind the question should be closed as is, and I didn't downvote. Why do you think you need to specify a type like this? What is the *actual* problem you want to solve? A data template is typically used to display *items* in a container, which is why you'll see strings as DataType but never IEnumerable. Containers are rendered by controls derived from ItemsControls which knows about IEnumerable already – Panagiotis Kanavos Jan 08 '19 at 13:37
  • And finally, you don't even need to specify `DataType` in a DataTemplate. WPF data binding works with reflection so you don't need to specify the type as long as the object the template binds to has properties that satisfy the binding expressions. – Panagiotis Kanavos Jan 08 '19 at 13:40
  • @Panagiotis Kanavos: I think you missed the point here. Besides that, the first paragraph aimed to all those people who said, that I should derive from `List` and create a non-generic type which closes `List`. Some example: https://stackoverflow.com/a/10005490/1353211. And if *almost all tutorials and docs* show binding to generic types anyway, why don't you just answer the question? – Rico-E Jan 08 '19 at 13:48
  • @Panagioti The question also makes no sense to me. However, in the example constructed in the question, the DataType property is necessary for automatic selection of a ContentTemplate of the ContentControl. – Clemens Jan 08 '19 at 13:48
  • @Rico-E It's entirely unclear why at all you would want to assign a collection instance to the Content property of a ContentControl. That doesn't seem to make much sense. Instead, you should assign the collection to the ItemsSource property of an ItemsControl, and have a DataTemplate (without DataType) as the ItemTemplate of the ItemsControl. – Clemens Jan 08 '19 at 13:55
  • 1
    @Clemens and that would still need an ItemsControl to display the items. Even if some conversion was needed, a *converter* would do the job. No need to create a markup extension – Panagiotis Kanavos Jan 08 '19 at 13:55
  • @Rico-E what you want is already available. Your assumptions are wrong - you can't use a generic type without specifying the type parameters. Your code doesn't do that anyway. I suspect you should check the tutorials on data binding. For example you *don't* set the content property directly. That binds the View to the ViewModel. You let data binding expressions bind each attribute to a type – Panagiotis Kanavos Jan 08 '19 at 13:58
  • @Rico-E there's nothing undocumented about *type names* either. People simply don't do what you tried to do though. Items typically have far more than two properties which makes it a lot easier to use ItemsControl, DataTemplate and bindings directly to property names – Panagiotis Kanavos Jan 08 '19 at 14:01
  • @Rico-E *someone else* downvoted this question. Everyone else abandoned it. Instead of picking on the only people that tried to help (Clemens and me), consider that the question wasn't clear until it was edited and people don't work in this way anyway – Panagiotis Kanavos Jan 10 '19 at 09:25
  • @Rico-E the *easy* way to bind to any container is to create a ViewModel class with a single property to contain the list or dictionary, and implement INotifyPropertyChanged. – Panagiotis Kanavos Jan 10 '19 at 09:27

1 Answers1

3

I got it to work with the following code:

Write a MarkupExtension which returns the closed generic type you want as DataType for your DataTemplate (this is not my own. It is somewhere from SO, but I didn't keep the link).

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

Use it as follows:

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:System="clr-namespace:System;assembly=mscorlib"
        xmlns:Generic="clr-namespace:System.Collections.Generic;assembly=mscorlib"
        xmlns:WpfApp1="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <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>
    <Grid>
        <ContentControl x:Name="_contentControl"/>
    </Grid>
</Window>

To see if the DataTemplate is automatically applied, use can write in code-behind:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        _contentControl.Content = new Dictionary<string, string>();
    }
}

And you will see your DataTemplate.

But in my project, I have a dedicated assembly for the styles in which I write all my DataTemplates and ControlTemplates. Usually I have a ResourceDictionary which holds them. But when I want to put my DataTemplate in a ResourceDictionary, the compiler tells me that it would not have a Key.

This does not work:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:System="clr-namespace:System;assembly=mscorlib"
                    xmlns:DataTemplates="clr-namespace:Dana.Styles.Flat.DataTemplates"
                    xmlns:Generic="clr-namespace:System.Collections.Generic;assembly=mscorlib">


    <x:Array Type="{x:Type System:Type}" 
             x:Key="ListWithTwoStringTypes">
        <x:Type TypeName="System:String" />
        <x:Type TypeName="System:String" />
    </x:Array>

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

</ResourceDictionary>

As a workaround, I am now defining my DataTemplates in the Resources of a FrameworkElement and add them in code-behind to the Application.Resources.

This is DictionaryStringString.xaml

<FrameworkElement x:Class="Dana.Styles.Flat.DataTemplates.DictionaryStringString"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:Generic="clr-namespace:System.Collections.Generic;assembly=mscorlib"
             xmlns:DataTemplates="clr-namespace:Dana.Styles.Flat.DataTemplates"
             xmlns:System="clr-namespace:System;assembly=mscorlib"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <FrameworkElement.Resources>

        <x:Array Type="{x:Type System:Type}" 
                 x:Key="ListWithTwoStringTypes">
            <x:Type TypeName="System:String" />
            <x:Type TypeName="System:String" />
        </x:Array>

        <DataTemplates:GenericType BaseType="{x:Type TypeName=Generic:Dictionary`2}" 
                                   InnerTypes="{StaticResource ListWithTwoStringTypes}"
                                   x:Key="DictionaryStringString" />

        <DataTemplate DataType="{StaticResource DictionaryStringString}">

            <TextBlock Text="Hallo Wörterbuch"
                           FontSize="40"
                           Foreground="Cyan"/>Template>
            </ItemsControl>-->
        </DataTemplate>
    </FrameworkElement.Resources>
</FrameworkElement>

This is DictionaryStringString.xaml.cs:

public partial class DictionaryStringString
{
    /// <summary>
    /// Konstruktor
    /// </summary>
    public DictionaryStringString()
    {
        InitializeComponent();
    }
}

And then, where I initialize my styles I added:

var _dictionaryStringString = new DictionaryStringString();
Application.Current.Resources.MergedDictionaries.Add(_dictionaryStringString.Resources);

And now I can define DataTemplates for all closed generic types and get them automatically applied by WPF =)

Rico-E
  • 1,806
  • 2
  • 28
  • 47
  • 1
    Hey, this method was perfect for my use case, and it worked the first time I ran my program, but next time, without any code changes, it failed with the build error: "All objects added to an IDictionary must have a Key attribute..." on the DataTemplate definition. I've tried building, rebuilding, anything I could think of, but the only way to get rid of the build error is to replace the DataType of the DataTemplate with a normal type :( Any ideas? `` – Frederik Nov 23 '21 at 12:46
  • TabViewData where T : IsTab OrderViewData : IsTab – Frederik Nov 23 '21 at 12:46
  • Okay I "Fixed" it by replacing the DataTemplate DataType assignment with ` ` The designer view breaks if I place the carret inside the forementioned code, but I can live with that. Hope someone else can use this :) – Frederik Nov 23 '21 at 13:12
  • 1
    Aaand it's broken again. I was working on other parts of my project, tried to build. Bam: "A key for a dictionary cannot be of type 'System.Windows.StaticResourceExtension'" I don't get it... Sorry for spamming your post. – Frederik Nov 23 '21 at 14:05
  • I created my own question: https://stackoverflow.com/questions/70082742/trying-to-implement-generic-types-binding-in-wpf-but-the-compiler-keeps-making – Frederik Nov 23 '21 at 14:40