我读过近千篇文章,解释在
DataType
上将封闭泛型类型设置为 DataTemplate
是行不通的,因为 WPF 不支持这一点。但事实上,这是错误的。
我可以在我的
DataTemplate
中定义以下Window.Resources
,当我将字符串列表分配给内容控件时将使用它。例如:
<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>
在代码隐藏中:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
_contentControl.Content = new List<string> { "Huhu" };
}
}
通过此设置,您将看到“Hi List of Strings”。对我来说,这证明我可以将泛型类型定义为
DataType
。但我想更进一步:我想将 Dictionary<string, string>
定义为 DataType
。但不幸的是,我无法让它工作。
所以问题是:如何将
Dictionary<string, string>
定义为DataType
的DataTemplate
?
如果您知道答案,则可以停止阅读。但由于展示我已经做过的事情是一个很好的做法,所以我继续写作。 我已经做了什么? 一开始我用蛮力尝试了几种类似的组合:
- 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]}"
但由于它们都不起作用,所以我深入研究
System.Xaml
并查看 TypeExtension
、GenericTypeNameParser
和 GenericTypeNameScanner
,因为我认为这些是解析类型的代码行。但查看代码我意识到 ` 是一个无效字符。
为了证明这一点,我自己写了
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();
}
}
并按如下方式使用它:
<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>
这引发了异常,即字符`不是预期的并且XAML类型无效。
这让我想知道为什么我的第一个例子有效。我认为,在为 WPF 标记编译 XAML 时,不是用于解析 XamlType 的
TypeExtension
,但我认为使用了 XamlNamespace
。因为此类具有使用 `- 字符的 MangleGenericTypeName
方法。
但我仍然看不到提取类型参数的代码,因此我看不到为字典指定类型参数的正确语法。这就是我被困住的地方。
(不用说,Microsoft 文档在这个主题上毫无价值。)
编辑:由于似乎不清楚我为什么想要这个,我将解释一下:我想要自动选择
ContentTemplate
中的 ContentControl
。当然:我在示例中构造的 DataTemplate
非常简单。但每个人都应该能够想象,我想要不同的 DataTemplates 用于列表、字典或简单字符串。
我有一个 ViewModel,它有一个
public object Result { get; }
属性。有时,结果是一个 int,有时是一个 string,有时是一个 List,等等。我将此 Result
属性绑定到 Content
的 ContentControl
属性。对于提到的所有类型,我编写了不同的 DataTemplates,它们由 WPF 自动选择。因此 int
显示在 Rectangle
中,而 String
显示在 Ellipse
中。
完成所有这些工作后,我想要另一个
DataTemplate
,但这次是 Dictionary
。
我让它可以使用以下代码:
编写一个
MarkupExtension
,它返回您想要的封闭通用类型作为 DataType
为您的 DataTemplate
(这不是我自己的。它来自 SO 的某个地方,但我没有保留链接)。
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;
}
}
使用方法如下:
<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>
要查看
DataTemplate
是否自动应用,可以在代码隐藏中编写:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
_contentControl.Content = new Dictionary<string, string>();
}
}
您将看到您的数据模板。
但在我的项目中,我有一个专门的样式集,我在其中编写了所有
DataTemplate
和 ControlTemplate
。通常我有一个 ResourceDictionary
来容纳它们。但是当我想将 DataTemplate
放入 ResourceDictionary
中时,编译器告诉我它没有 Key。
这不起作用:
<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>
作为解决方法,我现在在
DataTemplate
的 Resources
中定义我的 FrameworkElement
,并将它们添加到代码隐藏中的 Application.Resources
。
这是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>
这是DictionaryStringString.xaml.cs:
public partial class DictionaryStringString
{
/// <summary>
/// Konstruktor
/// </summary>
public DictionaryStringString()
{
InitializeComponent();
}
}
然后,在我初始化样式的地方添加:
var _dictionaryStringString = new DictionaryStringString();
Application.Current.Resources.MergedDictionaries.Add(_dictionaryStringString.Resources);
现在我可以为所有封闭泛型类型定义
DataTemplate
并让 WPF 自动应用它们 =)