问题
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 int
s are shown in a Rectangle
and String
s are shown in an Ellipse
.
After I got all this to work, I want another DataTemplate, but this time for a Dictionary.
回答1:
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 =)
来源:https://stackoverflow.com/questions/54092789/datatemplates-and-generics